Делегирование событий: от многих до одного
jQuery дает вам одну большую возможность. Вы можете быстро перебирать элементы, чтобы изменять что-нибудь в них или устанавливать обработчики событий. Но на самом деле это и не нужно. Делегирование событий[60] – необычайно мощный инструмент для создания веб-интерфейсов. По существу, вместо того, чтобы устанавливать обработчики событий на каждый элемент внутри главного элемента контента, вы назначаете один обработчик для основного содержимого. А это уже позволит браузерам упорядочить события.
Здесь есть несколько плюсов. Для начала вы покончите с установкой множества мелких обработчиков в документе, что всегда полезно для экономии памяти. Что еще интереснее, вы сделаете обработку событий независимой от количества используемых элементов. Например, если вы добавляете в ваш список дел новые пункты, вам вообще не нужно будет переназначать обработчики.
jQuery заимствовала этот принцип, когда добавила «живого» обработчика событий. Однако многие решения jQuery будут добавлять «живых» обработчиков к ID селекторам без детей. А по определению ID может появиться только один раз в документе и, поэтому, он не нуждается в делегировании.
Техники в примерах
Давайте применим эти техники в нескольких примерах. Начнем со списка дел, в котором используются делегирование событий и сгенерированный контент, потом перейдем к сайту-визитке, который использует перемещения, а в конце начнем применять HTML5 canvas для создания миниатюр в браузере.
Пример 1: Простой список дел
[61]
Чтобы создать список дел для всех браузеров, нам понадобится серверное решение для введения позиций списка, сохранения их в базе данных и отображения их в браузере. Мы не станем делать это здесь. Вместо этого мы просто используем решение на стороне клиента (в браузере), включающее хранение данных. Но с реальным продуктом вам потребуется резервный сервер.
Используя современные браузерные технологии, мы сможем сделать это в несколько строк кода, без каких-либо элементов цикла. Разметка в HTML достаточно проста:
<ul id="todolist"></ul>
<form action="#" method="post">
<div>
<label for="newitem">Add item</label>
<input type="text" name="newitem" id="newitem"
placeholder="new item">
<input type="submit" value="Add">
</div>
</form>
В коде JavaScript также нет ничего особенного:
var todo = document.querySelector('#todolist'),
form = document.querySelector('form'),
field = document.querySelector('#newitem');
form.addEventListener('submit', function(ev) {
var text = field.value;
if (text!== '') {
todo.innerHTML += '<li>' + text +'</li>';
field.value = '';
field.focus();
}
ev.preventDefault();
}, false);
todo.addEventListener('click', function(ev) {
var t = ev.target;
if (t.tagName === 'LI') {
t. parentNode.removeChild(t);
};
ev.preventDefault();
}, false);
Код, представленный выше, мы начинаем с ввода элементов в документ, который мы хотим создать, применяя querySelector. В данном случае мы будем вводить список, который добавит желаемые элементы, форму, в которой рождаются новые элементы, и поле, в которое вводится новая запись.
Потом мы добавим слушателя событий к форме, которая считывает значение поля и проверяет, было ли оно заполнено при отправке формы. (Это означает, что пользователь может добавлять новые позиции нажатием клавиши «Enter», помимо непосредственного добавления с помощью мыши. К сожалению, чересчур много jQuery решений используют вместо этого обработчик нажатия кнопку.) Если есть контент, мы добавляем новый элемент в список с помощью innerHTML. После этого мы удаляем текущий текст из поля ввода и ставим на него курсор (чтобы было легко добавлять другие элементы).
Чтобы пользователь мог удалять из списка завершенные дела, мы добавляем в него обработчик нажатия, считываем цель события и сравниваем его tagName с элементом li. Удаляем мы это с помощью старого метода DOM removeChild(). Это то, что нам нужно сделать для создания списка дел с неограниченным количеством элементов, используя делегирование событий. Ни больше ни меньше.
Продвинутые CSS селекторы и сгенерированный контент для задания стиля
Теперь мы хотим задать альтернативные цвета элементам списка. Еще мы хотим, чтобы при наведении курсора появлялся чекбокс (галочка), указывающая на то, что по клику список будет помечен как выполненный. Чтобы это сделать, нам не нужны ни JavaScript, ни изображения:
#todolist li {
background: #eee;
min-height: 20px;
position: relative;
}
#todolist li: nth-child(2n) {
background: #ccc;
}
#todolist li: hover: after {
content: '';
color: #060;
position: absolute;
right: 5px;
}
Рисунок 5.1. Наш список дел с «галочками»
Селектор nth-child(2n) указывает браузеру, что нужно окрасить каждую вторую строку в темно-серый цвет, а остальные оттенить светло-серым. Чтобы показать «галочку», когда пользователь проводит курсором над элементами, мы используем селектор: after и создаем «галочку» через таблицу символов UTF-8. Так как каждый элемент списка относительно позиционирован, любой абсолютно позиционированный «впадает» в него. Таким образом, правильное значение покажет зеленую «галочку» внутри блока, когда пользователь наведет курсор над элементом списка.
Делаем удаление элемента списка в два этапа
Что делать, если мы хотим, чтобы элементы в нашем перечне не только отмечались как завершенные, но и удалялись при втором клике? Все просто: мы всего лишь добавим другой режим и применим классы. Когда пользователь щелкает на элемент в первый раз, добавляется класс done, а когда второй раз, элемент удаляется. Все, что нам нужно сделать, – поменять обработчика событий:
todo.addEventListener('click', function(ev) {
var t = ev.target;
if (t.tagName === 'LI') {
if (t.classList.contains('done')) {
t. parentNode.removeChild(t);
} else {
t. classList.add('done');
}
};
ev.preventDefault();
}, false);
Рисунок 5.2. Список дел с «галочками» и иконкой удаления на втором этапе
Если элементу, на который мы кликаем, не присвоен класс done, он добавляется. Если же класс есть, мы удаляем элемент.
Это поддерживает функциональность, а также дает нам дополнительный класс для применения в нашей CSS. Мы можем использовать его для добавления подсказки об удалении (“x”), которая появляется, когда вы наводите курсор над завершенным элементом:
#todolist li: hover: after,
#todolist li.done: after {
content: '';
color: #060;
position: absolute;
right: 5px;
}
#todolist li.done: hover: after {
content: 'x';
font-weight: bold;
color: #c00;
position: absolute;
right: 5px;
}
Формы с проверкой корректного заполнения полей
Как вы помните, прежде чем создать новый перечень элементов, мы проверяем содержимое поля. Прямо сейчас это сделано на JavaScript. Но если мы храним верность среде, в которой творим, то можем обойтись и без этого. Добавление атрибута required в HTML гарантирует, что браузер проверит содержимое поля, прежде чем отправит форму:
<ul id="todolist"></ul>
<form action="#" method="post">
<div>
<label for="newitem">Add item</label>
<input type="text" name="newitem" id="newitem"
placeholder="new item" required>
<input type="submit" value="Add">
</div>
</form>
Если пользователь попытается отправить форму, не введя информацию, обработчик этого события точно не останется без работы. Это означает, что мы можем сократить код JavaScript очередной строкой:
form.addEventListener('submit', function(ev) {
todo.innerHTML += '<li>' + field.value + '</li>';
field.value = '';
field.focus();
ev.preventDefault();
}, false);
Рисунок 5.3. Firefox демонстрирует ошибку в заполнении поля и подсвечивает поле
Незаполненные поля автоматически помечаются браузером – что-то такое мы вынуждены были делать в прошлом (см. рис. 5.3). Если браузер не поддерживает атрибут required, форма будет отправлена. Вот что происходит, когда пользователь (или во многих случаях непрофессионал) исключает JavaScript. Тестирование в JavaScript – это удобное средство, но не мера защиты. В любом случае вам необходимо делать проверки и на сервере.
Сохранение состояния списка
Обычной процедурой в данный момент было бы найти способ сохранения информации в базе данных и запросить у пользователя учетные данные, чтобы сохранить их. Используя HTML5 и допуская, что это приложение будет использоваться на одном компьютере и его не нужно будет синхронизировать между многочисленными устройствами, мы можем использовать локальное хранилище для того, чтобы легко делать копию текущего состояниясписка, каждый раз, когда он меняется.