ok
This commit is contained in:
parent
aeb74092b6
commit
1f61c2ab1d
100 changed files with 4120 additions and 659 deletions
|
@ -0,0 +1,6 @@
|
|||
|
||||
The algorithm:
|
||||
1. Make `img` for every source.
|
||||
2. Add `onload/onerror` for every image.
|
||||
3. Increase the counter when either `onload` or `onerror` triggers.
|
||||
4. When the counter value equals to the sources count -- we're done: `callback()`.
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
let counter = 0;
|
||||
|
||||
function onLoad() {
|
||||
counter++;
|
||||
if (counter == sources.length) callback();
|
||||
}
|
||||
|
||||
for(let source of sources) {
|
||||
let img = document.createElement('img');
|
||||
img.onload = img.onerror = onLoad;
|
||||
img.src = source;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The test ----------
|
||||
|
||||
let sources = [
|
||||
"https://en.js.cx/images-load/1.jpg",
|
||||
"https://en.js.cx/images-load/2.jpg",
|
||||
"https://en.js.cx/images-load/3.jpg"
|
||||
];
|
||||
|
||||
// add random characters to prevent browser caching
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random();
|
||||
}
|
||||
|
||||
// for each image,
|
||||
// let's create another img with the same src and check that we have its width immediately
|
||||
function testLoaded() {
|
||||
let widthSum = 0;
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
let img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
alert(widthSum);
|
||||
}
|
||||
|
||||
// should output 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
/* your code */
|
||||
}
|
||||
|
||||
// ---------- The test ----------
|
||||
|
||||
let sources = [
|
||||
"https://en.js.cx/images-load/1.jpg",
|
||||
"https://en.js.cx/images-load/2.jpg",
|
||||
"https://en.js.cx/images-load/3.jpg"
|
||||
];
|
||||
|
||||
// add random characters to prevent browser caching
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random();
|
||||
}
|
||||
|
||||
// for each image,
|
||||
// let's create another img with the same src and check that we have its width immediately
|
||||
function testLoaded() {
|
||||
let widthSum = 0;
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
let img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
alert(widthSum);
|
||||
}
|
||||
|
||||
// every image is 100x100, the total width should be 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,36 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Load images with a callback
|
||||
|
||||
Normally, images are loaded when they are created. So i when we add `<img>` to the page, the user does not see the picture immediately. The browser needs to load it first.
|
||||
|
||||
To show an image immediately, we can create it "in advance", like this:
|
||||
|
||||
```js
|
||||
let img = document.createElement('img');
|
||||
img.src = 'my.jpg';
|
||||
```
|
||||
|
||||
The browser starts loading the image and remembers it in the cache. Later, when the same image appears in the document (no matter how), it shows up immediately.
|
||||
|
||||
**Create a function `preloadImages(sources, callback)` that loads all images from the array `sources` and, when ready, runs `callback`.**
|
||||
|
||||
For instance, this will show an `alert` after the images are loaded:
|
||||
|
||||
```js
|
||||
function loaded() {
|
||||
alert("Images loaded")
|
||||
}
|
||||
|
||||
preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);
|
||||
```
|
||||
|
||||
In case of an error, the function should still assume the picture "loaded".
|
||||
|
||||
In other words, the `callback` is executed when all images are either loaded or errored out.
|
||||
|
||||
The function is useful, for instance, when we plan to show a gallery with many scrollable images, and want to be sure that all images are loaded.
|
||||
|
||||
In the source document you can find links to test images, and also the code to check whether they are loaded or not. It should output `300`.
|
|
@ -1,8 +0,0 @@
|
|||
# Подсказка
|
||||
|
||||
Текст на странице пусть будет изначально `DIV`, с классом `img-replace` и атрибутом `data-src` для картинки.
|
||||
|
||||
Функция `replaceImg()` должна искать такие `DIV` и загружать изображение с указанным `src`. По `onload` осуществляется замена `DIV` на картинку.
|
||||
|
||||
# Решение
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.img-replace {
|
||||
float: left;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<button onclick="window.location.reload(true)">Перезагрузить ифрейм</button>
|
||||
<hr>
|
||||
<div style="width:114px;height:40px;font-size:32px;letter-spacing:3px" data-src="https://js.cx/search/google.png" class="img-replace">
|
||||
<span style="color:#1A53F7">G</span><span style="color:#E42131">o</span><span style="color:#FEB819">o</span><span style="color:#164AF2">g</span><span style="color:#00a315">l</span><span style="color:#E42131">e</span>
|
||||
</div>
|
||||
|
||||
<div style="width:101px;height:40px;font-size:32px" data-src="https://js.cx/search/yandex.png" class="img-replace">
|
||||
<span style="color:#F00">Я</span>ндекс
|
||||
</div>
|
||||
|
||||
<div style="width:100;height:40px;font-size:32px;color:#006dd4;font-weight:bold;letter-spacing:3px;font-family:Arial" data-src="bing.png" class="img-replace">bing</div>
|
||||
|
||||
|
||||
<script>
|
||||
function replaceImg() {
|
||||
var divs = document.querySelectorAll('div.img-replace');
|
||||
for (var i = 0; i < divs.length; i++)(function(i) {
|
||||
var img = document.createElement('img');
|
||||
img.src = divs[i].getAttribute('data-src');
|
||||
img.className = 'img-replace';
|
||||
|
||||
img.onload = function() {
|
||||
divs[i].parentNode.replaceChild(img, divs[i]);
|
||||
}
|
||||
|
||||
}(i))
|
||||
}
|
||||
|
||||
setTimeout(replaceImg, 1000); // задержка на 1 сек для демонстрации
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,40 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.img-replace {
|
||||
float: left;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Google -->
|
||||
|
||||
<div style="width:114px;height:40px;font-size:32px;letter-spacing:3px" class="img-replace">
|
||||
<span style="color:#1A53F7">G</span><span style="color:#E42131">o</span><span style="color:#FEB819">o</span><span style="color:#164AF2">g</span><span style="color:#00a315">l</span><span style="color:#E42131">e</span>
|
||||
</div>
|
||||
|
||||
<!-- Яндекс -->
|
||||
<div style="width:101px;height:40px;font-size:32px" class="img-replace">
|
||||
<span style="color:#F00">Я</span>ндекс
|
||||
</div>
|
||||
|
||||
<!-- Bing -->
|
||||
<div style="width:100;height:40px;font-size:32px;color:#006dd4;font-weight:bold;letter-spacing: 3px; font-family:Arial">bing</div>
|
||||
|
||||
<hr>
|
||||
<!-- картинки (для bing картинки специально нет, чтобы протестировать случай "загрузка не удалась") -->
|
||||
|
||||
<img src="https://js.cx/search/yandex.png" width="114" height="40" alt="Яндекс">
|
||||
<img src="https://js.cx/search/google.png" width="101" height="40" alt="Google">
|
||||
<img src="https://js.cx/search/bing.png" width="101" height="40" alt="Файла нет (bing)">
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Красивый "ALT"
|
||||
|
||||
Обычно, до того как изображение загрузится (или при отключенных картинках), посетитель видит пустое место с текстом из "ALT". Но этот атрибут не допускает HTML-форматирования.
|
||||
|
||||
При мобильном доступе скорость небольшая, и хочется, чтобы посетитель сразу видел красивый текст.
|
||||
|
||||
**Реализуйте "красивый" (HTML) аналог `alt` при помощи CSS/JavaScript, который затем будет заменён картинкой сразу же как только она загрузится.** А если загрузка не состоится -- то не заменён.
|
||||
|
||||
Демо: (нажмите "перезагрузить", чтобы увидеть процесс загрузки и замены)
|
||||
|
||||
[iframe src="solution" height="100"]
|
||||
|
||||
Картинки для `bing` специально нет, так что текст остается "как есть".
|
||||
|
||||
Исходный документ содержит разметку текста и ссылки на изображения.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
Создайте переменную-счетчик для подсчёта количества загруженных картинок, и увеличивайте при каждом `onload/onerror`.
|
||||
|
||||
Когда счетчик станет равен количеству картинок -- вызывайте `callback`.
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
var counter = 0;
|
||||
|
||||
function onLoad() {
|
||||
counter++;
|
||||
if (counter == sources.length) callback();
|
||||
}
|
||||
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var img = document.createElement('img');
|
||||
// сначала onload/onerror, затем src - важно для IE8-
|
||||
img.onload = img.onerror = onLoad;
|
||||
img.src = sources[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------- Проверка ----------
|
||||
|
||||
var sources = [
|
||||
"https://js.cx/images-load/1.jpg",
|
||||
"https://js.cx/images-load/2.jpg",
|
||||
"https://js.cx/images-load/3.jpg"
|
||||
];
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random();
|
||||
}
|
||||
|
||||
function testLoaded() {
|
||||
var widthSum = 0;
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
alert(widthSum); // 300!
|
||||
}
|
||||
|
||||
// до загрузки - 0
|
||||
testLoaded();
|
||||
// после загрузки - 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,50 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
/* ваш код */
|
||||
}
|
||||
|
||||
// ---------- Проверка ----------
|
||||
|
||||
/* файлы для загрузки */
|
||||
var sources = [
|
||||
"https://js.cx/images-load/1.jpg",
|
||||
"https://js.cx/images-load/2.jpg",
|
||||
"https://js.cx/images-load/3.jpg"
|
||||
];
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random(); // добавляем параметр, чтобы без кеша (для теста)
|
||||
}
|
||||
|
||||
/** если картинка загружена, то можно будет сразу получить её ширину
|
||||
* создадим все картинки и проверим, есть ли у них ширина
|
||||
*/
|
||||
function testLoaded() {
|
||||
var widthSum = 0;
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
// каждое изображение 100x100, общая ширина должна быть 300px
|
||||
alert(widthSum);
|
||||
}
|
||||
|
||||
// до загрузки - выведет 0
|
||||
testLoaded();
|
||||
|
||||
// после загрузки - выведет 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Загрузить изображения с коллбэком
|
||||
|
||||
Создайте функцию `preloadImages(sources, callback)`, которая предзагружает изображения из массива `sources`, и после загрузки вызывает функцию `callback`.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
preloadImages(["1.jpg", "2.jpg", "3.jpg"], callback);
|
||||
```
|
||||
|
||||
Если вдруг возникает ошибка при загрузке -- считаем такое изображение загруженным, чтобы не ломать поток выполнения.
|
||||
|
||||
Такая функция может полезна, например, для фоновой загрузки картинок в онлайн-галерею.
|
||||
|
||||
В исходном документе содержатся ссылки на картинки, а также код для проверки, действительно ли изображения загрузились. Он должен выводить "0", затем "300".
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Подсказка
|
||||
|
||||
Добавляйте `SCRIPT` при помощи методов `DOM`:
|
||||
|
||||
```js
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
|
||||
// в документе может не быть HEAD или BODY,
|
||||
// но хотя бы один (текущий) SCRIPT в документе есть
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(script, s); // перед ним и вставим
|
||||
```
|
||||
|
||||
На скрипт повесьте обработчики `onload/onreadystatechange`.
|
||||
|
||||
# Решение
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
function go() {
|
||||
alert("ok");
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function addScript(src, callback) {
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
var s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore(script, s);
|
||||
|
||||
var loaded = false;
|
||||
|
||||
function onload() {
|
||||
if (loaded) return; // повторный вызов
|
||||
loaded = true;
|
||||
callback();
|
||||
}
|
||||
|
||||
script.onload = onload; // все браузеры, IE с версии 9
|
||||
|
||||
script.onreadystatechange = function() { // IE8-
|
||||
if (this.readyState == 'loaded' || this.readyState == 'complete') {
|
||||
setTimeout(onload, 0);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
addScript("go.js", function() {
|
||||
go();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||
function go() {
|
||||
alert("ok");
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function addScript(src, callback) {
|
||||
/* ваш код */
|
||||
}
|
||||
|
||||
addScript("go.js", function() {
|
||||
go();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Скрипт с коллбэком
|
||||
|
||||
Создайте функцию `addScript(src, callback)`, которая загружает скрипт с данным `src`, и после его загрузки и выполнения вызывает функцию `callback`.
|
||||
|
||||
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
// go.js содержит функцию go()
|
||||
addScript("go.js", function() {
|
||||
go();
|
||||
});
|
||||
```
|
||||
|
||||
Ошибки загрузки обрабатывать не нужно.
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# Подсказки
|
||||
|
||||
Создайте переменную-счетчик для подсчёта количества загруженных скриптов.
|
||||
|
||||
Чтобы один скрипт не учитывался два раза (например, `onreadystatechange` запустился при `loaded` и `complete`), учитывайте его состояние в объекте `loaded`. Свойство `loaded[i] = true` означает что `i`-й скрипт уже учтён.
|
||||
|
||||
# Решение
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
function a() {
|
||||
b();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
function b() {
|
||||
c();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
function c() {
|
||||
alert('ok');
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function addScript(src) {
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
var s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore(script, s);
|
||||
return script;
|
||||
}
|
||||
|
||||
function addScripts(scripts, callback) {
|
||||
var loaded = {}; // Для загруженных файлов loaded[i] = true
|
||||
var counter = 0;
|
||||
|
||||
function onload(i) {
|
||||
if (loaded[i]) return; // лишний вызов onload/onreadystatechange
|
||||
loaded[i] = true;
|
||||
counter++;
|
||||
if (counter == scripts.length) callback();
|
||||
}
|
||||
|
||||
for (var i = 0; i < scripts.length; i++)(function(i) {
|
||||
var script = addScript(scripts[i]);
|
||||
|
||||
script.onload = function() {
|
||||
onload(i);
|
||||
};
|
||||
|
||||
script.onreadystatechange = function() { // IE8-
|
||||
if (this.readyState == 'loaded' || this.readyState == 'complete') {
|
||||
setTimeout(this.onload, 0); // возможны повторные вызовы onload
|
||||
}
|
||||
};
|
||||
|
||||
}(i));
|
||||
|
||||
}
|
||||
|
||||
addScripts(["a.js", "b.js", "c.js"], function() {
|
||||
a()
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||
function a() {
|
||||
b();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
function b() {
|
||||
c();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
function c() {
|
||||
alert('ok');
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
/* ваш код */
|
||||
|
||||
// функция a() сработает только если загружены a.js, b.js, c.js
|
||||
addScripts(["a.js", "b.js", "c.js"], function() {
|
||||
a()
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Скрипты с коллбэком
|
||||
|
||||
Создайте функцию `addScripts(scripts, callback)`, которая загружает скрипты из массива `scripts`, и *после загрузки и выполнения их всех* вызывает функцию `callback`.
|
||||
|
||||
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js no-beautify
|
||||
addScripts(["a.js", "b.js", "c.js"], function() { a() });
|
||||
/* функция a() описана в a.js и использует b.js,c.js */
|
||||
```
|
||||
|
||||
- Ошибки загрузки обрабатывать не нужно.</li>
|
||||
- Один скрипт не ждёт другого. Они все загружаются, а по окончании вызывается обработчик `callback`.
|
||||
|
||||
Исходный код содержит скрипты `a.js`, `b.js`, `c.js`:
|
||||
|
|
@ -1,227 +1,89 @@
|
|||
# Resource loading: onload and onerror
|
||||
|
||||
Браузер позволяет отслеживать загрузку внешних ресурсов -- скриптов, ифреймов, картинок и других.
|
||||
The browser allows to track the loading of external resources -- scripts, iframes, pictures and so on.
|
||||
|
||||
Для этого есть два события:
|
||||
There are two events for it:
|
||||
|
||||
- `onload` -- если загрузка успешна.
|
||||
- `onerror` -- если при загрузке произошла ошибка.
|
||||
- `onload` -- successful load,
|
||||
- `onerror` -- an error occured.
|
||||
|
||||
## Загрузка SCRIPT
|
||||
## Loading a script
|
||||
|
||||
Рассмотрим следующую задачу.
|
||||
Let's say we need to call a function that resides in an external script.
|
||||
|
||||
В браузере работает сложный интерфейс и, чтобы создать очередной компонент, нужно загрузить скрипт с сервера.
|
||||
|
||||
Подгрузить внешний скрипт -- достаточно просто:
|
||||
We can load it dynamically, like this:
|
||||
|
||||
```js
|
||||
var script = document.createElement('script');
|
||||
let script = document.createElement('script');
|
||||
script.src = "my.js";
|
||||
|
||||
document.body.appendChild(script);
|
||||
document.head.append(script);
|
||||
```
|
||||
|
||||
...Но как после подгрузки выполнить функцию, которая объявлена в этом скрипте? Для этого нужно отловить момент окончания загрузки и выполнения тега `<script>`.
|
||||
...But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.
|
||||
|
||||
### script.onload
|
||||
|
||||
Главным помощником станет событие `onload`. Оно сработает, когда скрипт загрузился и выполнился.
|
||||
The main helper is the `load` event. It triggers after the script was loaded and executed.
|
||||
|
||||
Например:
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
var script = document.createElement('script');
|
||||
```js run untrusted
|
||||
let script = document.createElement('script');
|
||||
|
||||
// can load any script, from any domain
|
||||
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
|
||||
document.body.appendChild(script);
|
||||
document.head.append(script);
|
||||
|
||||
*!*
|
||||
script.onload = function() {
|
||||
// после выполнения скрипта становится доступна функция _
|
||||
alert( _ ); // её код
|
||||
}
|
||||
// the script creates a helper function "_"
|
||||
alert(_); // the function is available
|
||||
};
|
||||
*/!*
|
||||
```
|
||||
|
||||
Это даёт возможность, как в примере выше, получить переменные из скрипта и выполнять объявленные в нём функции.
|
||||
So in `onload` we can use script variables, run functions etc.
|
||||
|
||||
...А что, если загрузка скрипта не удалась? Например, такого скрипта на сервере нет (ошибка 404) или сервер "упал" (ошибка 500).
|
||||
|
||||
Такую ситуацию тоже нужно как-то обрабатывать, хотя бы сообщить посетителю о возникшей проблеме.
|
||||
...And what if the loading failed? For instance, there's no such script (error 404) or the server or the server is down (unavailable).
|
||||
|
||||
### script.onerror
|
||||
|
||||
Любые ошибки загрузки (но не выполнения) скрипта отслеживаются обработчиком `onerror`.
|
||||
Errors that occur during the loading (but not execution) of the script can be tracked on `error` event.
|
||||
|
||||
Например, сделаем запрос заведомо отсутствующего скрипта:
|
||||
For instance, let's request a script that doesn't exist:
|
||||
|
||||
```js run
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://example.com/404.js"
|
||||
document.body.appendChild(script);
|
||||
let script = document.createElement('script');
|
||||
script.src = "https://example.com/404.js"; // no such script
|
||||
document.head.append(script);
|
||||
|
||||
*!*
|
||||
script.onerror = function() {
|
||||
alert( "Ошибка: " + this.src );
|
||||
alert("Error loading " + this.src); // Error loading https://example.com/404.js
|
||||
};
|
||||
*/!*
|
||||
```
|
||||
|
||||
### IE8-: script.onreadystatechange [#onreadystatechange]
|
||||
Please note that we can't get error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
|
||||
|
||||
Примеры выше работают во всех браузерах, кроме IE8-.
|
||||
## Other resources
|
||||
|
||||
В IE для отслеживания загрузки есть другое событие: `onreadystatechange`. Оно срабатывает многократно, при каждом обновлении состояния загрузки.
|
||||
The `load` and `error` events also work for other resources. There may be minor differences though.
|
||||
|
||||
Текущая стадия процесса находится в `script.readyState`:
|
||||
For instance:
|
||||
|
||||
`loading`
|
||||
: В процессе загрузки.
|
||||
|
||||
`loaded`
|
||||
: Получен ответ с сервера -- скрипт или ошибка. Скрипт на фазе `loaded` может быть ещё не выполнен.
|
||||
|
||||
`complete`
|
||||
: Скрипт выполнен.
|
||||
|
||||
Например, рабочий скрипт:
|
||||
|
||||
```js run no-beautify
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://code.jquery.com/jquery.js";
|
||||
document.documentElement.appendChild(script);
|
||||
|
||||
*!*
|
||||
script.onreadystatechange = function() {
|
||||
alert(this.readyState); // loading -> loaded -> complete
|
||||
}
|
||||
*/!*
|
||||
```
|
||||
|
||||
Скрипт с ошибкой:
|
||||
|
||||
```js run no-beautify
|
||||
var script = document.createElement('script');
|
||||
script.src = "http://ajax.googleapis.com/404.js";
|
||||
document.documentElement.appendChild(script);
|
||||
|
||||
*!*
|
||||
script.onreadystatechange = function() {
|
||||
alert(this.readyState); // loading -> loaded
|
||||
}
|
||||
*/!*
|
||||
```
|
||||
|
||||
Обратим внимание на две особенности:
|
||||
|
||||
- **Стадии могут пропускаться.**
|
||||
|
||||
Если скрипт в кэше браузера -- он сразу даст `complete`. Вы можете увидеть это, если несколько раз запустите первый пример.
|
||||
- **Нет особой стадии для ошибки.**
|
||||
|
||||
В примере выше это видно, обработка останавливается на `loaded`.
|
||||
|
||||
Итак, самое надёжное средство для IE8- поймать загрузку (или ошибку загрузки) -- это повесить обработчик на событие `onreadystatechange`, который будет срабатывать и на стадии `complete` и на стадии `loaded`. Так как скрипт может быть ещё не выполнен к этому моменту, то вызов функции лучше сделать через `setTimeout(.., 0)`.
|
||||
|
||||
Пример ниже вызывает `afterLoad` после загрузки скрипта и работает только в IE:
|
||||
|
||||
```js run no-beautify
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://code.jquery.com/jquery.js";
|
||||
document.documentElement.appendChild(script);
|
||||
|
||||
function afterLoad() {
|
||||
alert("Загрузка завершена: " + typeof(jQuery));
|
||||
}
|
||||
|
||||
*!*
|
||||
script.onreadystatechange = function() {
|
||||
if (this.readyState == "complete") { // на случай пропуска loaded
|
||||
afterLoad(); // (2)
|
||||
}
|
||||
|
||||
if (this.readyState == "loaded") {
|
||||
setTimeout(afterLoad, 0); // (1)
|
||||
|
||||
// убираем обработчик, чтобы не сработал на complete
|
||||
this.onreadystatechange = null;
|
||||
}
|
||||
}
|
||||
*/!*
|
||||
```
|
||||
|
||||
Вызов `(1)` выполнится при первой загрузке скрипта, а `(2)` -- при второй, когда он уже будет в кеше, и стадия станет сразу `complete`.
|
||||
|
||||
Функция `afterLoad` может и не обнаружить `jQuery`, если при загрузке была ошибка, причём не важно какая -- файл не найден или синтаксис скрипта ошибочен.
|
||||
|
||||
### Кросс-браузерное решение
|
||||
|
||||
Для кросс-браузерной обработки загрузки скрипта или её ошибки поставим обработчик на все три события: `onload`, `onerror`, `onreadystatechange`.
|
||||
|
||||
Пример ниже выполняет функцию `afterLoad` после загрузки скрипта *или* при ошибке.
|
||||
|
||||
Работает во всех браузерах:
|
||||
|
||||
```js run
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://code.jquery.com/jquery.js";
|
||||
document.documentElement.appendChild(script);
|
||||
|
||||
function afterLoad() {
|
||||
alert( "Загрузка завершена: " + typeof(jQuery) );
|
||||
}
|
||||
|
||||
script.onload = script.onerror = function() {
|
||||
if (!this.executed) { // выполнится только один раз
|
||||
this.executed = true;
|
||||
afterLoad();
|
||||
}
|
||||
};
|
||||
|
||||
script.onreadystatechange = function() {
|
||||
var self = this;
|
||||
if (this.readyState == "complete" || this.readyState == "loaded") {
|
||||
setTimeout(function() {
|
||||
self.onload()
|
||||
}, 0); // сохранить "this" для onload
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Загрузка других ресурсов
|
||||
|
||||
Поддержка этих событий для других типов ресурсов различна:
|
||||
|
||||
`<img>`, `<link>` (стили)
|
||||
: Поддерживает `onload/onerror` во всех браузерах.
|
||||
`<img>`, `<link>` (external stylesheets)
|
||||
: Both `load` and `error` events work as expected.
|
||||
|
||||
`<iframe>`
|
||||
: Поддерживает `onload` во всех браузерах. Это событие срабатывает как при успешной загрузке, так и при ошибке.
|
||||
: Only `load` event when the iframe loading finished. It triggers both for successful load and in case of an error. That's for historical reasons.
|
||||
|
||||
Обратим внимание, что если `<iframe>` загружается с того же домена, то можно, используя `iframe.contentWindow.document` получить ссылку на документ и поставить обработчик `DOMContentLoaded`. А вот если `<iframe>` -- с другого домена, то так не получится, однако сработает `onload`.
|
||||
## Summary
|
||||
|
||||
## Итого
|
||||
Pictures `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
|
||||
|
||||
В этой статье мы рассмотрели события `onload/onerror` для ресурсов.
|
||||
- `load` triggers on a successful load,
|
||||
- `error` triggers on a failed load.
|
||||
|
||||
Их можно обобщить, разделив на рецепты:
|
||||
|
||||
Отловить загрузку скрипта (включая ошибку)
|
||||
: Ставим обработчики на `onload` + `onerror` + (для IE8-) `onreadystatechange`, как указано в рецепте выше
|
||||
|
||||
Отловить загрузку картинки `<img>` или стиля `<link>`
|
||||
: Ставим обработчики на `onload` + `onerror`
|
||||
|
||||
```js no-beautify
|
||||
var img = document.createElement('img');
|
||||
img.onload = function() { alert("Успех "+this.src };
|
||||
img.onerror = function() { alert("Ошибка "+this.src };
|
||||
img.src = ...
|
||||
```
|
||||
|
||||
Изображения начинают загружаться сразу при создании, не нужно их для этого вставлять в HTML.
|
||||
|
||||
**Чтобы работало в IE8-, `src` нужно ставить *после* `onload/onerror`.**
|
||||
|
||||
Отловить загрузку `<iframe>`
|
||||
: Поддерживается только обработчик `onload`. Он сработает, когда `IFRAME` загрузится, со всеми подресурсами, а также в случае ошибки.
|
||||
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.
|
||||
|
|
|
@ -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>
|
||||
```
|
||||
|
|
@ -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. Сделайте её выбранной.
|
||||
|
276
2-ui/4-forms-controls/1-form-elements/article.md
Normal file
276
2-ui/4-forms-controls/1-form-elements/article.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
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).
|
BIN
2-ui/4-forms-controls/1-form-elements/form-navigation.png
Normal file
BIN
2-ui/4-forms-controls/1-form-elements/form-navigation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
2-ui/4-forms-controls/1-form-elements/form-navigation@2x.png
Normal file
BIN
2-ui/4-forms-controls/1-form-elements/form-navigation@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,3 @@
|
|||
В данном случае достаточно событий `input.focus/input.blur`.
|
||||
|
||||
Если бы мы хотели реализовать это на уровне документа, то применили бы делегирование и события `focusin/focusout` (эмуляцию для firefox), так как обычные `focus/blur` не всплывают.
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,20 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Улучшенный плейсхолдер
|
||||
|
||||
Реализуйте более удобный плейсхолдер-подсказку на JavaScript через атрибут `data-placeholder`.
|
||||
|
||||
Правила работы плейсхолдера:
|
||||
|
||||
- Элемент изначально содержит плейсхолдер. Специальный класс `placeholder` придает ему синий цвет.
|
||||
- При фокусировке плейсхолдер показывается уже над полем, становясь "подсказкой".
|
||||
- При снятии фокуса, подсказка убирается, если поле пустое -- плейсхолдер возвращается в него.
|
||||
|
||||
Демо:
|
||||
|
||||
[iframe src="solution" height=100]
|
||||
|
||||
В этой задаче плейсхолдер должен работать на одном конкретном input. Подумайте, если input много, как здесь применить делегирование?
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
Нам нужно ловить `onclick` на мышонке и в `onkeydown` на нём смотреть коды символов. При скан-кодах стрелок двигать мышонка через `position:absolute` или `position:fixed`.
|
||||
|
||||
Скан-коды для клавиш стрелок можно узнать, нажимая на них на [тестовом стенде](info:keyboard-events#keyboard-test-stand). Вот они: 37-38-39-40 (влево-вверх-вправо-вниз).
|
||||
|
||||
Проблема может возникнуть одна -- `keydown` не возникает на элементе, если на нём нет фокуса.
|
||||
|
||||
Чтобы фокус был -- нужно добавить мышонку атрибут `tabindex` через JS или в HTML.
|
||||
|
|
@ -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>
|
|
@ -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>
|
13
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/task.md
Normal file
13
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Мышонок на "клавиатурном" приводе
|
||||
|
||||
Кликните по мышонку. Затем нажимайте клавиши со стрелками, и он будет двигаться.
|
||||
|
||||
[demo src="solution"]
|
||||
|
||||
В этой задаче запрещается ставить обработчики куда-либо, кроме элемента `#mouse`.
|
||||
|
||||
Можно изменять атрибуты и классы в HTML.
|
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;
|
||||
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';
|
||||
}
|
||||
```
|
||||
|
|
@ -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>
|
|
@ -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 */
|
||||
}
|
|
@ -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>
|
|
@ -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 */
|
||||
}
|
14
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/task.md
Normal file
14
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/task.md
Normal 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"].
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
1. При клике -- заменяем `innerHTML` ячейки на `<textarea>` с размерами "под ячейку", без рамки.
|
||||
2. В `textarea.value` присваиваем содержимое ячейки.
|
||||
3. Фокусируем посетителя на ячейке вызовом `focus()`.
|
||||
4. Показываем кнопки OK/CANCEL под ячейкой.
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
/* ваши стили */
|
|
@ -0,0 +1,3 @@
|
|||
var table = document.getElementById('bagua-table');
|
||||
|
||||
/* ваш код */
|
16
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md
Normal file
16
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Редактирование TD по клику
|
||||
|
||||
Сделать ячейки таблицы `td` редактируемыми по клику.
|
||||
|
||||
- При клике -- ячейка `<td>` превращается в редактируемую, можно менять HTML. Размеры ячеек при этом не должны меняться.
|
||||
- В один момент может редактироваться одна ячейка.
|
||||
- При редактировании под ячейкой появляются кнопки для приема и отмена редактирования, только клик на них заканчивает редактирование.
|
||||
|
||||
Демо:
|
||||
|
||||
[iframe src="solution"]
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Красивый плейсхолдер для INPUT
|
||||
|
||||
Создайте для `<input type="password">` красивый, стилизованный плейсхолдер, например (кликните на тексте):
|
||||
|
||||
[iframe src="solution" height=90]
|
||||
|
||||
При клике плейсхолдер просто исчезает и дальше не показывается.
|
|
@ -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>
|
||||
```
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,12 @@
|
|||
importance: 3
|
||||
|
||||
---
|
||||
|
||||
# Поле, предупреждающее о включенном CapsLock
|
||||
|
||||
Создайте поле, которое будет предупреждать пользователя, если включен `key:CapsLock`. Выключение `key:CapsLock` уберёт предупреждение.
|
||||
|
||||
Такое поле может помочь избежать ошибок при вводе пароля.
|
||||
|
||||
[iframe height=80 src="solution"]
|
||||
|
322
2-ui/4-forms-controls/2-focus-blur/article.md
Normal file
322
2-ui/4-forms-controls/2-focus-blur/article.md
Normal 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`.
|
||||
```
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Алгоритм решения такой.
|
||||
|
||||
Только численный ввод в поле с суммой разрешаем, повесив обработчик на `keypress`.
|
||||
|
||||
Отслеживаем события изменения для перевычисления результатов:
|
||||
|
||||
- На `input`: событие `input` и дополнительно `propertychange/keyup` для совместимости со старыми IE.
|
||||
- На `checkbox`: событие `click` вместо `change` для совместимости с IE8-.
|
||||
- На `select`: событие `change`.
|
|
@ -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>
|
|
@ -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>
|
|
@ -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.
|
||||
- Изменения в форме отражаются в результатах сразу.
|
||||
|
185
2-ui/4-forms-controls/3-events-change/article.md
Normal file
185
2-ui/4-forms-controls/3-events-change/article.md
Normal 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` и аналогичные не всплывают. То есть, обработчики нужно назначать на сам элемент, без делегирования.
|
||||
|
|
@ -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'ом`. Мы не помещаем форму в контейнер, чтобы она не унаследовала полупрозрачность.
|
||||
|
|
@ -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>
|
|
@ -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>
|
33
2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md
Normal file
33
2-ui/4-forms-controls/4-forms-submit/1-modal-dialog/task.md
Normal 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-позиционированием - в песочнице.
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,17 @@
|
|||
importance: 3
|
||||
|
||||
---
|
||||
|
||||
# Валидация формы
|
||||
|
||||
Напишите функцию `validate(form)`, которая проверяет содержимое формы по клику на кнопку "Проверить".
|
||||
|
||||
Ошибки:
|
||||
|
||||
1. Одно из полей не заполнено.
|
||||
2. Пароли не совпадают.
|
||||
|
||||
Ошибка должна сопровождаться сообщением у поля. Например:
|
||||
|
||||
[iframe height=280 src="solution"]
|
||||
|
56
2-ui/4-forms-controls/4-forms-submit/article.md
Normal file
56
2-ui/4-forms-controls/4-forms-submit/article.md
Normal 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()`, то он выполнил все проверки.
|
||||
|
||||
Это используют, в частности, для искусственной генерации и отправки формы.
|
||||
|
3
2-ui/4-forms-controls/index.md
Normal file
3
2-ui/4-forms-controls/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Forms, controls
|
||||
|
||||
Special properties and events for forms `<form>` and controls: `<input>`, `<select>` and other.
|
Loading…
Add table
Add a link
Reference in a new issue