ok
This commit is contained in:
parent
003272d77b
commit
cf9969f7ab
26 changed files with 344 additions and 538 deletions
|
@ -32,7 +32,7 @@
|
|||
|
||||
|
||||
<div id="field">
|
||||
<img src="https://en.js.gripe/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
|
||||
<div id="field">
|
||||
<img src="https://en.js.gripe/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
Поставьте обработчик `click` на контейнере. Он должен проверять, произошел ли клик на кнопке удаления (`target`), и если да, то удалять соответствующий ей `DIV`.
|
||||
|
|
@ -8,38 +8,33 @@
|
|||
|
||||
<body>
|
||||
|
||||
<div id="messages-container">
|
||||
<div id="container">
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<h3>Horse</h3>
|
||||
<p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<h3>Donkey</h3>
|
||||
<p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют
|
||||
тёлками.
|
||||
<h3>Cat</h3>
|
||||
<p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin.
|
||||
</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
var container = document.getElementById('messages-container');
|
||||
|
||||
container.onclick = function(event) {
|
||||
if (!event.target.classList.contains('remove-button')) return;
|
||||
if (event.target.className != 'remove-button') return;
|
||||
|
||||
event.target.parentNode.hidden = !event.target.parentNode.hidden;
|
||||
}
|
||||
let pane = event.target.closest('.pane');
|
||||
pane.remove();
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -2,32 +2,34 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="messages.css">
|
||||
<link rel="stylesheet" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Кнопка для удаления:
|
||||
<button class="remove-button">[x]</button>
|
||||
|
||||
<div>
|
||||
<div id="container">
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<h3>Horse</h3>
|
||||
<p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<h3>Donkey</h3>
|
||||
<p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют
|
||||
тёлками.
|
||||
<h3>Cat</h3>
|
||||
<p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin.
|
||||
</p>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
// ...your code...
|
||||
</script>
|
||||
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -6,6 +6,7 @@ body {
|
|||
h3 {
|
||||
margin: 0;
|
||||
padding-bottom: .3em;
|
||||
padding-right: 20px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
|
@ -18,15 +19,19 @@ p {
|
|||
background: #edf5e1;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
font-size: 110%;
|
||||
top: 0;
|
||||
color: darkred;
|
||||
right: 10px;
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@ importance: 5
|
|||
|
||||
---
|
||||
|
||||
# Скрытие сообщения с помощью делегирования
|
||||
# Hide messages with delegation
|
||||
|
||||
Дан список сообщений. Добавьте каждому сообщению кнопку для его удаления.
|
||||
There's a list of messages with removal buttons `[x]`. Make the buttons work.
|
||||
|
||||
**Используйте делегирование событий. Один обработчик для всего.**
|
||||
|
||||
В результате, должно работать вот так(кликните на крестик):
|
||||
Like this:
|
||||
|
||||
[iframe src="solution" height=420]
|
||||
|
||||
P.S. Should be only one event listener on the container, use event delegation.
|
||||
|
|
|
@ -1,110 +1,4 @@
|
|||
# Схема решения
|
||||
|
||||
Дерево устроено как вложенный список.
|
||||
|
||||
Клики на все элементы можно поймать, повесив единый обработчик `onclick` на внешний `UL`.
|
||||
|
||||
Как поймать клик на заголовке? Элемент `LI` является блочным, поэтому нельзя понять, был ли клик на *тексте*, или справа от него.
|
||||
|
||||
Например, ниже -- участок дерева с выделенными рамкой узлами. Кликните справа от любого заголовка. Видите, клик ловится? А лучше бы такие клики (не на тексте) игнорировать.
|
||||
|
||||
```html autorun height=190 untrusted
|
||||
<style>
|
||||
li {
|
||||
border: 1px solid green;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul onclick="alert(event.target)">
|
||||
<li>Млекопетающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
В примере выше видно, что проблема в верстке, в том что `LI` занимает всю ширину. Можно кликнуть справа от текста, это все еще `LI`.
|
||||
|
||||
Один из способов это поправить -- обернуть заголовки в дополнительный элемент `SPAN`, и обрабатывать только клики внутри `SPAN'ов`, получать по `SPAN'у` его родителя `LI` и ставить ему класс открыт/закрыт.
|
||||
|
||||
Напишите для этого JavaScript-код.
|
||||
|
||||
# Оборачиваем заголовки в SPAN
|
||||
|
||||
Следующий код ищет все `LI` и оборачивает текстовые узлы в `SPAN`.
|
||||
|
||||
```js
|
||||
var treeUl = document.getElementsByTagName('ul')[0];
|
||||
|
||||
var treeLis = treeUl.getElementsByTagName('li');
|
||||
|
||||
for (var i = 0; i < treeLis.length; i++) {
|
||||
var li = treeLis[i];
|
||||
|
||||
var span = document.createElement('span');
|
||||
li.insertBefore(span, li.firstChild); // добавить пустой SPAN
|
||||
span.appendChild(span.nextSibling); // переместить в него заголовок
|
||||
}
|
||||
```
|
||||
|
||||
Теперь можно отслеживать клики *на заголовках*.
|
||||
|
||||
Так выглядит дерево с обёрнутыми в `SPAN` заголовками и делегированием:
|
||||
|
||||
```html autorun height=190 untrusted
|
||||
<style>
|
||||
span {
|
||||
border: 1px solid red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul onclick="alert(event.target.tagName)">
|
||||
<li><span>Млекопетающие</span>
|
||||
<ul>
|
||||
<li><span>Коровы</span></li>
|
||||
<li><span>Ослы</span></li>
|
||||
<li><span>Собаки</span></li>
|
||||
<li><span>Тигры</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Так как `SPAN` -- инлайновый элемент, он всегда такого же размера как текст. Да здравствует `SPAN`!
|
||||
|
||||
В реальной жизни дерево, скорее всего, будет сразу со `SPAN`: если HTML-код дерева генерируется на сервере, то это несложно, если дерево генерируется в JavaScript -- тем более просто.
|
||||
|
||||
# Итоговое решение
|
||||
|
||||
Для делегирования нужно по клику понять, на каком узле он произошел.
|
||||
|
||||
В нашем случае у `SPAN` нет детей-элементов, поэтому не нужно подниматься вверх по цепочке родителей. Достаточно просто проверить `event.target.tagName == 'SPAN'`, чтобы понять, где был клик, и спрятать потомков.
|
||||
|
||||
```js
|
||||
var tree = document.getElementsByTagName('ul')[0];
|
||||
|
||||
tree.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
if (target.tagName != 'SPAN') {
|
||||
return; // клик был не на заголовке
|
||||
}
|
||||
|
||||
var li = target.parentNode; // получить родительский LI
|
||||
|
||||
// получить UL с потомками -- это первый UL внутри LI
|
||||
var childrenContainer = li.getElementsByTagName('ul')[0];
|
||||
|
||||
if (!childrenContainer) return; // потомков нет -- ничего не надо делать
|
||||
|
||||
// спрятать/показать (можно и через CSS-класс)
|
||||
childrenContainer.hidden = !childrenContainer.hidden;
|
||||
}
|
||||
```
|
||||
|
||||
Выделение узлов жирным при наведении делается при помощи CSS-селектора `:hover`.
|
||||
The solution has two parts.
|
||||
|
||||
1. Wrap every tree node title into `<span>`. Then we can CSS-style them on `:hover` and handle clicks exactly on text, because `<span>` width is exactly the text width (unlike without it).
|
||||
2. Set a handler to the `tree` root node and handle clicks on that `<span>` titles.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
.tree span:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.tree span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -16,38 +16,37 @@
|
|||
|
||||
<body>
|
||||
|
||||
<ul class="tree">
|
||||
<li>Животные
|
||||
<ul class="tree" id="tree">
|
||||
<li>Animals
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<li>Mammals
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
<li>Cows</li>
|
||||
<li>Donkeys</li>
|
||||
<li>Dogs</li>
|
||||
<li>Tigers</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<li>Other
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
<li>Snakes</li>
|
||||
<li>Birds</li>
|
||||
<li>Lizards</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<li>Fishes
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<li>Aquarium
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
<li>Guppy</li>
|
||||
<li>Angelfish</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<li>Sea
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
<li>Sea trout</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -55,29 +54,22 @@
|
|||
</ul>
|
||||
|
||||
<script>
|
||||
var tree = document.getElementsByTagName('ul')[0];
|
||||
|
||||
var treeLis = tree.getElementsByTagName('li');
|
||||
|
||||
/* wrap all textNodes into spans */
|
||||
for (var i = 0; i < treeLis.length; i++) {
|
||||
var li = treeLis[i];
|
||||
|
||||
var span = document.createElement('span');
|
||||
li.insertBefore(span, li.firstChild);
|
||||
span.appendChild(span.nextSibling);
|
||||
// move all text into <span>
|
||||
// they occupy exactly the place necessary for the text,
|
||||
for (let li of tree.querySelectorAll('li')) {
|
||||
let span = document.createElement('span');
|
||||
li.prepend(span);
|
||||
span.append(span.nextSibling); // move the text node into span
|
||||
}
|
||||
|
||||
/* catch clicks on whole tree */
|
||||
// catch clicks on whole tree
|
||||
tree.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
if (target.tagName != 'SPAN') {
|
||||
if (event.target.tagName != 'SPAN') {
|
||||
return;
|
||||
}
|
||||
|
||||
/* now we know the SPAN is clicked */
|
||||
var childrenContainer = target.parentNode.getElementsByTagName('ul')[0];
|
||||
let childrenContainer = target.parentNode.querySelector('ul');
|
||||
if (!childrenContainer) return; // no children
|
||||
|
||||
childrenContainer.hidden = !childrenContainer.hidden;
|
||||
|
@ -85,5 +77,4 @@
|
|||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,44 +1,41 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<ul class="tree">
|
||||
<li>Животные
|
||||
<ul class="tree" id="tree">
|
||||
<li>Animals
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<li>Mammals
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
<li>Cows</li>
|
||||
<li>Donkeys</li>
|
||||
<li>Dogs</li>
|
||||
<li>Tigers</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<li>Other
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
<li>Snakes</li>
|
||||
<li>Birds</li>
|
||||
<li>Lizards</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<li>Fishes
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<li>Aquarium
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
<li>Guppy</li>
|
||||
<li>Angelfish</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<li>Sea
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
<li>Sea trout</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -46,5 +43,4 @@
|
|||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -2,17 +2,13 @@ importance: 5
|
|||
|
||||
---
|
||||
|
||||
# Раскрывающееся дерево
|
||||
# Tree menu
|
||||
|
||||
Создайте дерево, которое по клику на заголовок скрывает-показывает детей:
|
||||
Create a tree that shows/hides node children on click:
|
||||
|
||||
[iframe border=1 src="solution"]
|
||||
|
||||
Требования:
|
||||
|
||||
- Использовать делегирование.
|
||||
- Клик вне текста заголовка (на пустом месте) ничего делать не должен.
|
||||
- При наведении на заголовок -- он становится жирным, реализовать через CSS.
|
||||
|
||||
P.S. При необходимости HTML/CSS дерева можно изменить.
|
||||
Requirements:
|
||||
|
||||
- Only one event handler (use delegation)
|
||||
- A click outside the node title (on an empty space) should not do anything.
|
||||
|
|
|
@ -1,14 +1 @@
|
|||
# Подсказка (обработчик)
|
||||
|
||||
1. Обработчик `onclick` можно повесить один, на всю таблицу или `THEAD`. Он будет игнорировать клики не на `TH`.
|
||||
2. При клике на `TH` обработчик будет получать номер из `TH`, на котором кликнули (`TH.cellIndex`) и вызывать функцию `sortColumn`, передавая ей номер колонки и тип.
|
||||
3. Функция `sortColumn(colNum, type)` будет сортировать.
|
||||
|
||||
# Подсказка (сортировка)
|
||||
|
||||
Функция сортировки:
|
||||
|
||||
1. Переносит все `TR` из `TBODY` в массив `rowsArr`
|
||||
2. Сортирует массив, используя `rowsArr.sort(compare)`, функция `compare` зависит от типа столбца.
|
||||
3. Добавляет `TR` из массива обратно в `TBODY`
|
||||
|
||||
|
|
|
@ -4,13 +4,19 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid black;
|
||||
padding: 4px;
|
||||
}
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -19,57 +25,54 @@
|
|||
<table id="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-type="number">Возраст</th>
|
||||
<th data-type="string">Имя</th>
|
||||
<th data-type="number">Age</th>
|
||||
<th data-type="string">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Вася</td>
|
||||
<td>John</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Петя</td>
|
||||
<td>Pete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Женя</td>
|
||||
<td>Ann</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Маша</td>
|
||||
<td>Eugene</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Илья</td>
|
||||
<td>Ilya</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
// сортировка таблицы
|
||||
// использовать делегирование!
|
||||
// должно быть масштабируемо:
|
||||
// код работает без изменений при добавлении новых столбцов и строк
|
||||
|
||||
var grid = document.getElementById('grid');
|
||||
|
||||
grid.onclick = function(e) {
|
||||
if (e.target.tagName != 'TH') return;
|
||||
|
||||
// Если TH -- сортируем
|
||||
sortGrid(e.target.cellIndex, e.target.getAttribute('data-type'));
|
||||
let th = e.target;
|
||||
// if TH, then sort
|
||||
// cellIndex is the number of th:
|
||||
// 0 for the first column
|
||||
// 1 for the second column, etc
|
||||
sortGrid(th.cellIndex, th.dataset.type);
|
||||
};
|
||||
|
||||
function sortGrid(colNum, type) {
|
||||
var tbody = grid.getElementsByTagName('tbody')[0];
|
||||
let tbody = grid.querySelector('tbody');
|
||||
|
||||
// Составить массив из TR
|
||||
var rowsArray = [].slice.call(tbody.rows);
|
||||
let rowsArray = Array.from(tbody.rows);
|
||||
|
||||
// определить функцию сравнения, в зависимости от типа
|
||||
var compare;
|
||||
// compare(a, b) compares two rows, need for sorting
|
||||
let compare;
|
||||
|
||||
switch (type) {
|
||||
case 'number':
|
||||
|
@ -87,20 +90,9 @@
|
|||
// сортировать
|
||||
rowsArray.sort(compare);
|
||||
|
||||
// Убрать tbody из большого DOM документа для лучшей производительности
|
||||
grid.removeChild(tbody);
|
||||
|
||||
// добавить результат в нужном порядке в TBODY
|
||||
// они автоматически будут убраны со старых мест и вставлены в правильном порядке
|
||||
for (var i = 0; i < rowsArray.length; i++) {
|
||||
tbody.appendChild(rowsArray[i]);
|
||||
}
|
||||
|
||||
grid.appendChild(tbody);
|
||||
|
||||
tbody.append(...rowsArray);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -4,13 +4,19 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid black;
|
||||
padding: 4px;
|
||||
}
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -19,38 +25,37 @@
|
|||
<table id="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-type="number">Возраст</th>
|
||||
<th data-type="string">Имя</th>
|
||||
<th data-type="number">Age</th>
|
||||
<th data-type="string">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Вася</td>
|
||||
<td>John</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Петя</td>
|
||||
<td>Pete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Женя</td>
|
||||
<td>Ann</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Маша</td>
|
||||
<td>Eugene</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Илья</td>
|
||||
<td>Ilya</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
/* ваш код */
|
||||
// ...your code...
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -2,19 +2,42 @@ importance: 4
|
|||
|
||||
---
|
||||
|
||||
# Сортировка таблицы
|
||||
# Sortable table
|
||||
|
||||
Сделать сортировку таблицы при клике на заголовок.
|
||||
Make the table sortable: clicks on `<th>` elements should sort it by corresponding column.
|
||||
|
||||
Демо:
|
||||
Each `<th>` has the type in the attribute, like this:
|
||||
|
||||
[iframe border=1 src="solution" height=180]
|
||||
```html
|
||||
<table id="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
*!*
|
||||
<th data-type="number">Age</th>
|
||||
<th data-type="string">Name</th>
|
||||
*/!*
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>John</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Ann</td>
|
||||
</tr>
|
||||
...
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
Требования:
|
||||
In the example above the first column has numbers, and the second one -- strings. The sorting function should handle sort according to the type.
|
||||
|
||||
- Использовать делегирование.
|
||||
- Код не должен меняться при увеличении количества столбцов или строк.
|
||||
Only `"string"` and `"number"` types should be supported.
|
||||
|
||||
P.S. Обратите внимание, тип столбца задан атрибутом у заголовка. Это необходимо, ведь числа сортируются иначе чем строки. Соответственно, код это может использовать.
|
||||
The working example:
|
||||
|
||||
P.P.S. Вам помогут дополнительные [навигационные ссылки по таблицам](info:traversing-dom#dom-navigation-tables).
|
||||
[iframe border=1 src="solution" height=190]
|
||||
|
||||
P.S. The table can be big, with any number of rows and columns.
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px;
|
||||
/* make body scrollable, the tooltip should work after the scroll */
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid #b3c9ce;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font: italic 14px/1.3 arial, sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
|
||||
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
|
||||
|
||||
<button data-tooltip="the tooltip is longer than the element">Short button</button>
|
||||
<button data-tooltip="HTML<br>tooltip">One more button</button>
|
||||
|
||||
<p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p>
|
||||
|
||||
|
||||
<script>
|
||||
let tooltipElem;
|
||||
|
||||
document.onmouseover = function(event) {
|
||||
let target = event.target;
|
||||
|
||||
// if we have tooltip HTML...
|
||||
let tooltipHtml = target.dataset.tooltip;
|
||||
if (!tooltipHtml) return;
|
||||
|
||||
// ...create the tooltip element
|
||||
|
||||
tooltipElem = document.createElement('div');
|
||||
tooltipElem.className = 'tooltip';
|
||||
tooltipElem.innerHTML = tooltipHtml;
|
||||
document.body.append(tooltipElem);
|
||||
|
||||
// position it above the annotated element (top-center)
|
||||
let coords = target.getBoundingClientRect();
|
||||
|
||||
let left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth) / 2;
|
||||
if (left < 0) left = 0; // don't cross the left window edge
|
||||
|
||||
let top = coords.top - tooltipElem.offsetHeight - 5;
|
||||
if (top < 0) { // if crossing the top window edge, show below instead
|
||||
top = coords.top + target.offsetHeight + 5;
|
||||
}
|
||||
|
||||
tooltipElem.style.left = left + 'px';
|
||||
tooltipElem.style.top = top + 'px';
|
||||
};
|
||||
|
||||
document.onmouseout = function(e) {
|
||||
|
||||
if (tooltipElem) {
|
||||
tooltipElem.remove();
|
||||
tooltipElem = null;
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px;
|
||||
/* make body scrollable, the tooltip should work after the scroll */
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
/* some styles for the tooltip, you can use your own instead */
|
||||
position: fixed;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid #b3c9ce;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font: italic 14px/1.3 arial, sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
|
||||
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
|
||||
|
||||
<button data-tooltip="the tooltip is longer than the element">Short button</button>
|
||||
<button data-tooltip="HTML<br>tooltip">One more button</button>
|
||||
|
||||
<p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p>
|
||||
|
||||
|
||||
<script>
|
||||
// ...your code...
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
36
2-ui/2-events/5-event-delegation/4-behavior-tooltip/task.md
Normal file
36
2-ui/2-events/5-event-delegation/4-behavior-tooltip/task.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Tooltip behavior
|
||||
|
||||
Create JS-code for the tooltip behavior.
|
||||
|
||||
When a mouse comes over an element with `data-tooltip`, the tooltip should appear over it, and when it's gone then hide.
|
||||
|
||||
An example of annotated HTML:
|
||||
```html
|
||||
<button data-tooltip="the tooltip is longer than the element">Short button</button>
|
||||
<button data-tooltip="HTML<br>tooltip">One more button</button>
|
||||
```
|
||||
|
||||
Should work like this:
|
||||
|
||||
[iframe src="solution" height=200 border=1]
|
||||
|
||||
In this task we assume that all elements with `data-tooltip` have only text inside. No nested tags.
|
||||
|
||||
Details:
|
||||
|
||||
- The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it.
|
||||
- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML.
|
||||
|
||||
You'll need two events here:
|
||||
- `mouseover` triggers when a pointer comes over an element.
|
||||
- `mouseout` triggers when a pointer leaves an element.
|
||||
|
||||
Please use event delegation: set up two handlers on `document` to track all "overs" and "outs" from elements with `data-tooltip` and manage tooltips from there.
|
||||
|
||||
After the behavior is implemented, even people unfamiliar with JavaScript can add annotated elements.
|
||||
|
||||
P.S. To keep things natural and simple: only one tooltip may show up at a time.
|
|
@ -240,6 +240,9 @@ Let's note once again what we did. Now, to add toggling functionality to an elem
|
|||
|
||||
That may become really convenient -- no need to write JavaScript for every such element. Just use the behavior. The document-level handler makes it work for any element of the page.
|
||||
|
||||
We can combine multiple behaviors on a single element as well.
|
||||
|
||||
The "behavior" pattern can be an alternative of mini-fragments of JavaScript.
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px;
|
||||
/* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
padding: 10px 20px;
|
||||
/* красивости... */
|
||||
|
||||
border: 1px solid #b3c9ce;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font: italic 14px/1.3 arial, sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
|
||||
<p>Прокрутите страницу, чтобы ссылки были вверху и проверьте, правильно ли показываются подсказки.</p>
|
||||
|
||||
|
||||
<script>
|
||||
var showingTooltip;
|
||||
|
||||
document.onmouseover = function(e) {
|
||||
var target = e.target;
|
||||
|
||||
var tooltip = target.getAttribute('data-tooltip');
|
||||
if (!tooltip) return;
|
||||
|
||||
var tooltipElem = document.createElement('div');
|
||||
tooltipElem.className = 'tooltip';
|
||||
tooltipElem.innerHTML = tooltip;
|
||||
document.body.appendChild(tooltipElem);
|
||||
|
||||
var coords = target.getBoundingClientRect();
|
||||
|
||||
var left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth) / 2;
|
||||
if (left < 0) left = 0; // не вылезать за левую границу окна
|
||||
|
||||
var top = coords.top - tooltipElem.offsetHeight - 5;
|
||||
if (top < 0) { // не вылезать за верхнюю границу окна
|
||||
top = coords.top + target.offsetHeight + 5;
|
||||
}
|
||||
|
||||
tooltipElem.style.left = left + 'px';
|
||||
tooltipElem.style.top = top + 'px';
|
||||
|
||||
showingTooltip = tooltipElem;
|
||||
};
|
||||
|
||||
document.onmouseout = function(e) {
|
||||
|
||||
if (showingTooltip) {
|
||||
document.body.removeChild(showingTooltip);
|
||||
showingTooltip = null;
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,32 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px;
|
||||
/* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
/* ваши стили */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
|
||||
<p>Прокрутите страницу, чтобы ссылки были вверху и проверьте, правильно ли показываются подсказки.</p>
|
||||
|
||||
|
||||
<script>
|
||||
// ваш код
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,33 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Поведение "подсказка"
|
||||
|
||||
При наведении мыши на элемент, на нём возникает событие `mouseover`, при удалении мыши с элемента -- событие `mouseout`.
|
||||
|
||||
Зная это, напишите JS-код, который будет делать так, что при наведении на элемент, если у него есть атрибут `data-tooltip` -- над ним будет показываться подсказка с содержимым этого атрибута.
|
||||
|
||||
Например, две кнопки:
|
||||
|
||||
```html
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
```
|
||||
|
||||
Результат в ифрейме с документом:
|
||||
|
||||
[iframe src="solution" height=200 border=1]
|
||||
|
||||
В этой задаче можно полагать, что в элементе с атрибутом `data-tooltip` -- только текст, то есть нет подэлементов.
|
||||
|
||||
Детали оформления:
|
||||
|
||||
- Подсказка должна появляться при наведении на элемент, по центру и на небольшом расстоянии сверху. При уходе курсора с элемента -- исчезать.
|
||||
- Текст подсказки брать из значения атрибута `data-tooltip`. Это может быть произвольный HTML.
|
||||
- Оформление подсказки должно задаваться CSS.
|
||||
- Подсказка не должна вылезать за границы экрана, в том числе если страница частично прокручена. Если нельзя показать сверху -- показывать снизу элемента.
|
||||
|
||||
Важно: нужно использовать приём разработки "поведение", то есть поставить обработчик (точнее два) на `document`, а не на каждый элемент.
|
||||
|
||||
Плюс этого подхода -- динамически добавленные в DOM позже элементы автоматически получат этот функционал.
|
|
@ -1,93 +0,0 @@
|
|||
# Приём проектирования "поведение"
|
||||
|
||||
Шаблон проектирования "поведение" (behavior) позволяет задавать хитрые обработчики на элементе *декларативно*, установкой специальных HTML-атрибутов и классов.
|
||||
|
||||
[cut]
|
||||
|
||||
## Описание
|
||||
|
||||
Приём проектирования "поведение" состоит из двух частей:
|
||||
|
||||
1. Элементу ставится атрибут, описывающий его поведение.
|
||||
2. При помощи делегирования ставится обработчик на документ, который ловит все клики и, если элемент имеет нужный атрибут, производит нужное действие.
|
||||
|
||||
## Пример
|
||||
|
||||
Например, добавим "поведение", которое всем элементам, у которых стоит атрибут `data-counter`, будет при клике увеличивать значение на `1`:
|
||||
|
||||
```html run autorun height=60
|
||||
Счётчик:
|
||||
<button data-counter>1</button>
|
||||
Ещё счётчик:
|
||||
<button data-counter>2</button>
|
||||
|
||||
<script>
|
||||
document.onclick = function(event) {
|
||||
if (!event.target.hasAttribute('data-counter')) return;
|
||||
|
||||
var counter = event.target;
|
||||
|
||||
counter.innerHTML++;
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Если запустить HTML-код выше, то при клике на каждую кнопку -- её значение будет увеличиваться.
|
||||
|
||||
Конечно, нам важны не счётчики, а общий подход, который они демонстрируют.
|
||||
|
||||
Элементов `data-counter` может быть сколько угодно. Новые могут добавляться в HTML в любой момент. При помощи делегирования мы, фактически, добавили новый "псевдо-стандартный" атрибут в HTML, который добавляет элементу новую возможность ("поведение").
|
||||
|
||||
## Ещё пример
|
||||
|
||||
Добавим ещё поведение.
|
||||
|
||||
Сделаем так, что при клике на элемент с атрибутом `data-toggle-id` будет скрываться/показываться элемент с заданным `id`:
|
||||
|
||||
```html autorun run height=60
|
||||
<button *!*data-toggle-id="subscribe-mail"*/!*>
|
||||
Показать форму подписки
|
||||
</button>
|
||||
|
||||
<form id="subscribe-mail" hidden>
|
||||
Ваша почта: <input type="email">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
document.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
var id = target.getAttribute('data-toggle-id');
|
||||
if (!id) return;
|
||||
|
||||
var elem = document.getElementById(id);
|
||||
|
||||
elem.hidden = !elem.hidden;
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
Ещё раз заметим, что мы сделали. Теперь для того, чтобы добавить скрытие-раскрытие любому элементу -- даже не надо знать JavaScript, можно просто написать атрибут `data-toggle-id`.
|
||||
|
||||
Это бывает очень удобно -- не нужно писать JavaScript-код для каждого элемента, который должен служить такой кнопкой. Просто используем поведение.
|
||||
|
||||
Обратите внимание: обработчик поставлен на `document`, клик на любом элементе страницы пройдёт через него, так что поведение определено глобально.
|
||||
|
||||
```smart header="Не только атрибут"
|
||||
Для своих целей мы можем использовать в HTML любые атрибуты, но стандарт рекомендует для своих целей называть атрибуты `data-*`.
|
||||
|
||||
В обработчике `document.onclick` мы могли бы проверять не атрибут, а класс или что-то ещё, но с атрибутом -- проще и понятнее всего.
|
||||
|
||||
Также для добавления обработчиков на `document` рекомендуется использовать `addEventListener`, чтобы можно было добавить более одного обработчика для типа события.
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
Шаблон "поведение" удобен тем, что сколь угодно сложное JavaScript-поведение можно "навесить" на элемент одним лишь атрибутом. А можно -- несколькими атрибутами на связанных элементах.
|
||||
|
||||
Здесь мы рассмотрели базовый пример, который можно как угодно модифицировать и масштабировать. Важно не переусердствовать.
|
||||
|
||||
Приём разработки "поведение" рекомендуется использовать для расширения возможностей разметки, как альтернативу мини-фрагментам JavaScript.
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue