Вы находитесь на странице: 1из 272

С.

Пьюривал
Основы разработки веб-приложений
Серия «Бестселлеры O’Reilly»
Перевел c английского О. Сивченко

Заведующий редакцией Д. Виницкий


Ведущий редактор Н. Гринчик
Литературный редактор Н. Рощина
Художник В. Шимкевич
Корректоры Т. Курьянович, Е. Павлович
Верстка А. Засулевич

ББК 32.988.02
УДК 004.738.5
Пьюривал С.
П96 Основы разработки веб-приложений. — СПб.: Питер, 2015. — 272 с.: ил. — (Се-
рия «Бестселлеры O’Reilly»).
ISBN 978-5-496-01226-3
Благодаря этой книге вы усвоите основы создания веб-приложений, построив простое приложение
с нуля с помощью HTML, JavaScript и других свободно предоставляемых инструментов. Это практи-
ческое руководство на реальных примерах обучает неопытных веб-разработчиков тому, как создавать
пользовательский интерфейс, строить серверную часть, организовывать связь клиента и сервера,
а также применять облачные сервисы для развертывания приложения.
Каждая глава содержит практические задачи, полноценные примеры, а также ментальные модели
процесса разработки. Эта книга поможет вам сделать первые шаги в создании веб-приложений, обес­
печив глубокие знания по теме.
12+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ)

ISBN 978-1449370190 англ. Authorized Russian translation of the English edition Learning Web App Development
(ISBN 9781449370190) © 2014 Semmy Purewal. This translation is published and sold
by permission of O’Reilly Media, Inc., the owner of all rights to publish and sell the same.
ISBN 978-5-496-01226-3 © Перевод на русский язык ООО Издательство «Питер», 2015
© Издание на русском языке, оформление ООО Издательство «Питер», 2015

Права на издание получены по соглашению с O’Reilly. Все права защищены. Никакая часть данной книги не
может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских
прав.

Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как на-
дежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может
гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные
ошибки, связанные с использованием книги.

ООО «Питер Пресс», 192102, Санкт-Петербург, ул. Андреевская (д. Волкова), д. 3, литер А, пом. 7Н.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные
профессиональные, технические и научные.
Подписано в печать 19.09.14. Формат 70×100/16. Усл. п. л. 21,930. Тираж 1000. Заказ 0000.
Отпечатано в полном соответствии с качеством предоставленных издательством материалов
в ГППО «Псковская областная типография». 180004, Псков, ул. Ротная, 34.
$ ............................................................................................12

........................................................................................................12

..................................................................................................12

º Рабочий процесс.................................................................................20

º Структура............................................................................................43

º Стиль..................................................................................................68

º Интерактивность............................................................................... 108

º Мост.................................................................................................. 155

º Сервер.............................................................................................. 185

º Хранение данных.............................................................................. 217

º Платформа........................................................................................ 234

º Приложение...................................................................................... 248
$ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Выбор технологии. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Поможет ли вам эта книга. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Занятия с книгой. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Преподавание с этой книгой. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Куда обратиться в случае затруднений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Общие комментарии к коду. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Условные обозначения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Использование примеров кода. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Safari® Books Online. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Как с нами связаться. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Выражения признательности. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

º Рабочий процесс . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Текстовые редакторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Установка Sublime Text. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Основы Sublime Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Контроль версий. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Установка Git. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Основы работы с командной строкой в UNIX . . . . . . . . . . . . . . . . . . . . 26
Основы Git. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Браузеры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Заучивание. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Sublime Text. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Emacs и Vim. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Командная строка UNIX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Узнайте больше о Git. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
GitHub. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Оглавление

º Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Привет, HTML!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Теги и содержание . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Тег <p>: aбзацы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Комментарии. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Заголовки, ссылки и списки... ох!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Объектная модель документа и древовидная модель. . . . . . . . . . . . . . . . . . . . 50
Использование валидации HTML для выявления проблем . . . . . . . . . . . . . . . . 51
Amazeriffic. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Определение структуры. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Визуализация структуры с помощью древовидной диаграммы . . . . . . . 57
Реализация структуры в ходе рабочего процесса. . . . . . . . . . . . . . . . . 58
Структурирование основной части. . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Структурирование подвала . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Заучивание. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Древовидные диаграммы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Составление страницы ВиО (FAQ) для Amazeriffic. . . . . . . . . . . . . . . . . 67
Больше об HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

º Стиль. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Привет, CSS!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Наборы правил . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Комментарии. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Отступы, границы и поля. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Селекторы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Псевдокласс . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Более сложные селекторы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Каскадные правила. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Наследование. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Плавающая компоновка. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Свойство clear. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Работа со шрифтами. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Устранение браузерной несовместимости . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Использование CSS Lint для выявления возможных проблем . . . . . . . . . . . . . . 91
Взаимодействие и решение проблем с Chrome Developer Tools. . . . . . . . . . . . . 93
Стилизуем Amazeriffic!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Сетка. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Создание колонок. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Добавление шрифтов и управление ими . . . . . . . . . . . . . . . . . . . . . . 104
Еще несколько изменений. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Оглавление

Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104


Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Заучивание. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Упражнения в CSS-селекторах. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Задайте стили для страницы ВиО для Amazeriffic. . . . . . . . . . . . . . . . 106
Каскадные правила. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Адаптивность и библиотеки адаптивности. . . . . . . . . . . . . . . . . . . . . 107

º Интерактивность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Привет, JavaScript!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108
Первое интерактивное приложение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Стиль . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Интерактивность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Общие сведения о jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Создание проекта . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Комментарии. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Селекторы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Управление элементами DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Общие характеристики JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .129
Работа с JavaScript в Chrome JavaScript Console . . . . . . . . . . . . . . . . . 129
Переменные и типы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Условия. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Повторение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Использование JSLint для выявления возможных проблем. . . . . . . . . . . . . . . 137
Добавление интерактивности Amazeriffic. . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Приступим. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Структура и стиль. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Интерактивность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Заучивание. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Плагины jQuery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Селекторы jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Задача FizzBuzz. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Упражнения в работе с массивами. . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Проект Эйлера (Project Euler). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Другие материалы по JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

º Мост . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Привет, объекты JavaScript!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Оглавление

Представление карточной игры. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155


Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Обмен информацией между компьютерами. . . . . . . . . . . . . . . . . . . . . . . . . . 159
JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
AJAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Доступ к внешнему файлу JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Ограничения браузера по безопасности. . . . . . . . . . . . . . . . . . . . . . . 161
Функция getJSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Массив JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Что же дальше?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Получение изображений с Flickr. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Добавление теговой функциональности в Amazeriffic. . . . . . . . . . . . . . . . . . . 168
Функция map. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Добавление вкладки Теги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Создание пользовательского интерфейса. . . . . . . . . . . . . . . . . . . . . . 171
Создание промежуточной структуры данных о тегах . . . . . . . . . . . . . 174
Теги как часть входных данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Слайд-шоу Flickr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Упражняемся в работе с объектами. . . . . . . . . . . . . . . . . . . . . . . . . . 181
Другие API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

º Сервер. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Настройка рабочего окружения. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Установка Virtual Box и Vagrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Создание виртуальной машины. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Подключение к виртуальной машине с помощью SSH. . . . . . . . . . . . . 188
Привет, Node.js!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .189
Ментальные модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Клиенты и серверы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Хосты и гости . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Практические вопросы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Привет, HTTP!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Модули и Express. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Установка Express с помощью NPM. . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Первый сервер Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Отправка клиентского приложения . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Общие принципы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Считаем твиты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Получение данных для входа в Twitter. . . . . . . . . . . . . . . . . . . . . . . . 199
Подключение к Twitter API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Как это получилось? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Оглавление

Хранение счетчиков. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201


Разделение счетчиков Twitter на модули . . . . . . . . . . . . . . . . . . . . . . 203
Импорт модуля в Express. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Настройка клиента . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Создание сервера для Amazeriffic. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Настройка папок. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Создание хранилища Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Создание сервера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Запуск сервера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Размещение информации на сервере. . . . . . . . . . . . . . . . . . . . . . . . . 208
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Локальная установка Node.js. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
JSHint и CSS Lint через NPM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Обсудим код счетчика твитов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .212
API покерного приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

º Хранение данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217


SQL и не-SQL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Redis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Взаимодействие с Redis через клиентскую командную строку. . . . . . . 219
Установка модуля Redis через файл package.json . . . . . . . . . . . . . . . 220
Взаимодействие с Redis в коде. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Установление начального значения счетчиков из хранилища Redis . . 222
Использование mget для получения нескольких величин. . . . . . . . . . 224
MongoDB. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Взаимодействие с MongoDB из клиента с интерфейсом
командной строки. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Моделирование данных с Mongoose. . . . . . . . . . . . . . . . . . . . . . . . . . 228
Хранение списка задач для Amazeriffic. . . . . . . . . . . . . . . . . . . . . . . . 231
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Покерное API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Другие источники информации о базах данных . . . . . . . . . . . . . . . . . 233

º Платформа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Cloud Foundry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Регистрация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Подготовка приложений к развертыванию в Сети . . . . . . . . . . . . . . . . . . . . . 235
Развертывание приложения. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .236
Получение информации о приложениях. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Обновление приложения. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Удаление приложений из Cloud Foundry. . . . . . . . . . . . . . . . . . . . . . . 240
Оглавление

Взаимозависимости и package.json. . . . . . . . . . . . . . . . . . . . . . . . . . . 241


Привязка Redis к приложению. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Привязка MongoDB к приложению. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Покерное API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Другие платформы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247

º Приложение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .248
Переработка клиента . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Обобщение основных принципов действия . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Введение AJAX для работы с вкладками. . . . . . . . . . . . . . . . . . . . . . . 251
Избавление от костылей совместимости. . . . . . . . . . . . . . . . . . . . . . . 253
Обработка ошибок AJAX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
Переработка серверного кода. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Организация кода. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Выражения HTTP, CRUD и REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Настройка маршрутов через ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Использование jQuery для прокладки и удаления маршрутов. . . . . . . 260
Коды ответов HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Шаблон «модель — представление — контроллер». . . . . . . . . . . . . . 262
Добавление пользователей в Amazeriffic . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Построение модели пользователей . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Построение контроллера пользователей . . . . . . . . . . . . . . . . . . . . . . 264
Настройка маршрутов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
Совершенствуем действия контроллера ToDo. . . . . . . . . . . . . . . . . . . 267
Подведем итоги. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Больше теории и практики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Удаление элементов списка задач. . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Добавление пользовательской панели администратора. . . . . . . . . . . 271
Представления с использованием EJS и Jade. . . . . . . . . . . . . . . . . . . 272
Создание нового приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Ruby on Rails. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
$
Ваши замечания, предложения и вопросы отправляйте по адресу электронной
почты vinitski@minsk.piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На сайте издательства http://www.piter.com вы найдете подробную информацию
о наших книгах.

Сэмми Пьюривал около 10 лет преподавал компьютерные дисциплины и работал


фрилансером-консультантом по JavaScript. За это время он успел поработать с раз-
нообразными группами клиентов, включая стартапы, некоммерческие организации
и исследовательские центры. Сейчас его основная работа — инженер-программист
в Сан-Хосе, Калифорния.

В начале 2008 года, через шесть лет после окончания школы и работы учителем на
полставки, мне очень хотелось стать преподавателем компьютерных дисциплин на
полный день. Очень быстро выяснилось, что место преподавателя найти нелегко,
а получение хорошей работы зависит от удачи в большей степени, чем от чего-либо
еще. Ну что ж, я поступил так, как поступает любой уважающий себя академик,
столкнувшись с удручающим положением на академическом рынке труда, а имен-
но: решил повысить свою конкурентоспособность с помощью изучения разработки
веб-приложений.
Это, конечно, звучит странно. Кроме всего прочего, к тому моменту я уже около
девяти лет изучал компьютерные дисциплины и, более того, свыше шести лет учил
студентов разрабатывать программное обеспечение (ПО). Разве я не должен был
хорошо знать, как создавать веб-приложения? Похоже, что нет, так как существу-
ет определенный разрыв между практической ежедневной работой по разработке
ПО и программированием как учебной дисциплиной, изучаемой в колледжах и уни-
верситетах. Фактически мои знания по веб-разработке были ограничены HTML
и в некоторой степени CSS, который я в то время изучал самостоятельно.
Предисловие

К счастью, у меня было несколько друзей, которые активно работали в компью-


терном мире, и большинство из них в то время обсуждало (относительно) новый
фреймворк1, который назывался Ruby on Rails. Мне показалось, что это весьма под-
ходящая область для развития, так что я купил несколько книг по этой теме и при-
нялся читать обучающие материалы в Интернете, чтобы побыстрее освоиться.
А через несколько месяцев, пытаясь хоть чего-нибудь добиться на практике,
я чуть было не сдался.
Почему? Да потому, что большинство книг и учебных статей начиналось с пред-
положения, что я уже умею создавать веб-приложения и делаю это на протяжении
нескольких лет! А между тем, несмотря на мой солидный теоретический багаж по
компьютерному программированию, оказалось, что все эти материалы слишком
лаконичны и очень сложны для понимания. Например, выяснилось, что можно
пройти несколько классов по компьютерным дисциплинам, ни разу не столкну­
вшись с шаблоном проектирования Model — View — Controller, а в некоторых
книгах уже в первой главе предполагается, что вы прекрасно с ним знакомы.
Тем не менее мне удалось изучить веб-разработку на уровне, достаточном для
того, чтобы несколько раз провести консультации, которые оказались весьма кста-
ти, пока я не получил должность преподавателя. Благодаря этому я заметил, что
меня настолько увлекают практические стороны данной области, что я продолжил
заниматься консультированием, одновременно работая учителем.
Через несколько лет занятий тем и другим мне предложили вести мой первый
класс по разработке веб-приложений в Университете Северной Каролины в Эшвил-
ле. Изначально я планировал начать с Ruby on Rails, но, взявшись за новейшие
книги и обучающие материалы по ней, выяснил, что они никак не улучшились за все
эти годы. Нет, они были хорошим подспорьем для людей, которые отлично знают
основы, но для студентов, которые у меня учились, они определенно не годились.
Грустно, но неудивительно — академические книги по веб-разработке оказались
еще хуже. Большинство из них содержали устаревшие концепции и не раскрывали
важнейших тем, нужных для понимания платформ наподобие Ruby on Rails. Мне
даже случилось выступить рецензентом одной книги, переизданной в 2011 году
и до сих пор описывающей верстку с помощью таблиц и тег <font>!
Что ж, у меня не было другого выхода, кроме как создавать свой курс с нуля и пи-
сать все материалы самостоятельно. В то время я проводил небольшую консульта-
ционную работу по Node.js (адаптация JavaScript для стороны сервера) и подумал,
что было бы интересно попробовать создать курс, обучающий одному и тому же
языку и для клиента, и для сервера. Более того, я поставил себе цель дать моим
студентам достаточно знаний для самостоятельного изучения Ruby on Rails, если
они решат продолжить.
Эта книга содержит большую часть материалов, созданных мной во время пре-
подавания этого курса в Университете Северной Каролины в Эшвилле. В ней

1
Фреймворк — структура программной системы, а также специальное ПО, с помощью
которого можно разрабатывать и объединять компоненты программного проекта. —
Примеч. пер.
Предисловие

описано, как создать простое веб-приложение на основе базы данных с нуля, исполь-
зуя JavaScript. Сюда включены описание простейшего рабочего процесса (с исполь-
зованием текстового редактора и системы контроля версий), основы технологий
клиентской стороны (HTML, CSS, jQuery, Javascript), основы серверных технологий
(Node.js, HTTP, базы данных), основы облачного развертывания (Cloud Foundry)
и несколько примеров правильной практики написания кода (функции, MVC, DRY).
Во время нашего пути мы исследуем фундаментальные основы языка JavaScript,
научимся программировать, используя объекты и массивы, а также рассмотрим
ментальные модели, которые соответствуют этому типу разработки ПО.

Œ#
Для контроля версий я выбрал Git, потому что… ну хорошо, это Git, и он прекрасен.
Кроме того, он дал моим студентам возможность изучить GitHub, который наби-
рает все большую популярность. Хотя я не рассматриваю GitHub в этой книге,
разобраться с ним совсем несложно, как только вы освоитесь с Git.
Я решил использовать для клиента jQuery, потому что он все еще остается по-
пулярным и мне очень нравится с ним работать. Я сам не использую никаких других
фреймворков для клиента, хотя и упоминаю Twitter Bootstrap и Zurb Foundation
в главе 3. Я решил не касаться современных клиентских фреймворков вроде Backbone
или Ember, потому что считаю их слишком сложными для начинающих. А вот с Rails
вы легко сможете начать работать после прочтения этой книги.
Для серверной стороны я выбрал Express, так как он (относительно) упрощенный
и не догматический. Я решил также не рассматривать шаблоны клиентской и сервер-
ной стороны, поскольку считаю, что вначале важно научиться делать это вручную.
Я не рассматриваю и реляционные базы данных, так как вряд ли возможно пол-
ноценно раскрыть эту тему в течение времени, выделенного на нее в рамках курса.
Реляционным базам данных я предпочел MongoDB из-за того, что они широко ис-
пользуются в сообществе Node.js и применяются JavaScript в качестве языка запро-
сов. Кроме того, мне очень нравится Redis, поэтому его мы также изучим.
Я выбрал Cloud Foundry в качестве платформы для развертывания, потому что,
как я выяснил (вместе с Heroku и Nodejitsu), она была одной из трех, предлагающих
бесплатное использование и не требующих кредитной карты для настройки вне-
шних сервисов. В любом случае различия между платформами незначительны и пе-
реход с одной на другую не потребует больших усилий.

%
Эта книга не предназначена для того, чтобы сделать вас «ниндзя», или «суперзвез-
дой», или даже хорошим компьютерным программистом. Она не подготовит вас
к немедленному трудоустройству, и я даже не обещаю показать вам «правильный
путь» в работе.
Преподавание с этой книгой

В то же время книга даст вам глубокое знание основ, которые необходимы для
понимания того, как отдельные части современного веб-приложения взаимодей­
ствуют друг с другом, и дадут вам стартовую точку для дальнейшего изучения темы.
Если вы как следует проработаете материал книги, то будете знать все, что в свое
время нужно было мне для начала изучения Rails.
Вы извлечете больше всего пользы из этой книги, если у вас есть небольшой
опыт программирования и нет никакого опыта в веб-разработке. Как минимум вы
должны быть знакомы с основными программными конструкциями, такими как
схемы if-else, циклы, переменные и типы данных. Впрочем, я не жду, что у вас есть
какой-то опыт в объектно-ориентированном программировании или каком-то
конкретном языке программмирования. Вы можете получить необходимые знания,
изучив материалы в Khan Academy или Code Academy или пройдя курс програм-
мирования в ближайшем колледже.
Я надеюсь, что эта книга может быть использована не только для самостоятель-
ного изучения, но и в качестве учебного материала в общественных классах по
разработке веб-приложений или, возможно, как курс для одного семестра (14 не-
дель) в колледже.

’’ 
Разработка веб-приложений — очень нужный вам навык. Держа это в уме, я писал
книгу, рассчитывая, что ее будут читать активно. Это значит, что самого лучшего
результата вы достигнете, читая ее около компьютера и по-настоящему набирая все
примеры. Конечно, такой подход связан с некоторым риском — всегда есть опасность,
что примеры кода не будут работать, если вы не наберете их точно так, как они на-
писаны в книге. Чтобы снизить риск, я создал хранилище GitHub со всеми приме-
рами из этой книги в рабочем состоянии. Вы можете посмотреть их в Интернете,
перейдя по ссылке http://www.github.com/semmypurewal/LearningWebAppDev. Посколь-
ку все полные примеры находятся там, я постараюсь избегать вставки на страницы
книги избыточного кода.
Кроме того, значительную часть примеров я оставил незаконченными. По моему
мнению, вам полезно будет закончить их самостоятельно. Я советую сделать это
прежде, чем смотреть на законченные примеры, размещенные в Интернете. В каж-
дой главе находится также некоторое количество практических задач и указателей
на источники информации, так что рекомендую вам поработать и над ними.

 %
Преподавая этот материал в 14-недельном курсе, я обычно уделял 2–3 недели
материалу первых трех глав и 3–4 недели — материалу последних трех. Это значит,
что больше всего времени я тратил на средние три главы, которые охватывают
программирование на JavaScript, jQuery, AJAX и Node.js. Студенты, которых я учу,
Предисловие

испытывают больше всего трудностей с массивами и объектами, поэтому я уделяю


дополнительное время этой теме, так как она очень важна для компьютерного
программирования в целом.
Определенно, я объясняю свой предмет более компьютерно-научным способом,
чем большинство книг на эти темы, поэтому данное издание хорошо подходит для
курса компьютерного программирования. В частности, я уделяю внимание мен-
тальным моделям, таким как дерево или иерархическая система, а также стараюсь
выделить функциональные подходы к программированию, если это имеет смысл
(хотя и стараюсь не подчеркивать этого в лекции). Если вы преподаете информа-
тику, то можете уделить этим аспектам материала больше внимания.
Я не собираюсь публиковать решения практических задач (хотя, возможно,
сделаю это, если получу очень много запросов), поэтому вы легко можете назначать
их в качестве домашнего задания и внеклассной работы.

$’ 
Как я уже говорил, у нас есть хранилище GitHub, которое содержит все примеры
кода, приведенные в этой книге. Кроме того, вы можете узнавать о найденных
опечатках и необходимых обновлениях на http://learningwebappdev.com.
Я также попытаюсь быть доступным для контакта и буду очень рад оказать
вам необходимую помощь. Пожалуйста, не стесняйтесь обращаться в мой Twitter
(@semmypurewal) с короткими вопросами или комментариями, а также писать в любое
время на электронный адрес me@semmy.me, если у вас более обширный вопрос. Я так-
же предлагаю вам пользоваться функциональностью issues в нашем хранилище GitHub,
чтобы задавать вопросы. Я приложу все усилия, чтобы отвечать как можно быстрее.

! 
Я старался писать код одновременно идиоматическим способом и настолько ясно,
насколько это возможно. Так что у меня были две противоречащие друг другу цели,
в результате чего в некоторых случаях мои решения в коде неидеальны — по педа-
гогическим причинам. Надеюсь, что эти примеры очевидны для опытных разра-
ботчиков и не причинят каких-либо трудностей начинающим в долгосрочной
перспективе.
Весь код работает корректно в современных браузерах, я протестировал его
полностью в Google Chrome. Однако не могу гарантировать, что все будет работать
хорошо в старых версиях Internet Explorer. Пожалуйста, дайте мне знать, если
найдете дефекты браузерной совместимости в Internet Explorer 10+ или современ-
ной версии любого другого браузера.
В большинстве случаев я следовал идиомам JavaScript, однако в некоторых
случаях пришлось отклониться. Например, для текстовых строк я предпочитаю
двойные, а не одинарные кавычки в первую очередь потому, что предполагаю
Условные обозначения

наличие у моих студентов знания теоретических основ Java/C++. Я использую


кавычки для названий свойств объектных констант, в результате чего JSON не
очень отличается от объектов JavaScript. Кроме того, я ставлю $ первым символом
в переменных, которые указывают на объекты jQuery. Мне кажется, это делает код
более ясным и легкочитаемым для новичков.

ß#’
В книге применяются следующие условные обозначения.
Курсивный шрифт
Им обозначаются новые термины и понятия.
Шрифт для названий
Используется для обозначения URL, адресов электронной почты, а также соче-
таний клавиш и названий элементов интерфейса.
Шрифт для команд
Применяется для обозначения программных элементов — переменных и названий
функций, типов данных, переменных окружения, операторов, ключевых слов и т. д.
Шрифт для листингов

Используется в листингах программного кода.


Шрифт для листингов полужирный

Обозначает команды или другой текст, который должен быть введен пользова-
телем.
Шрифт для листингов курсивный

Обозначает текст, который должен быть заменен величинами, введенными


пользователем или определенными из контекста.

Этот элемент означает совет или рекомендацию.

Такой элемент означает общее замечание.

Этот элемент означает предупреждение или предостережение.


Предисловие

$
Дополнительный материал (примеры кода, упражнения и т. д.) доступен для за-
грузки по адресу http://www.github.com/semmypurewal/LearningWebAppDev.
Эта книга создана, чтобы помочь вам выполнить свою работу. Если пример кода
предлагается в книге, можете использовать его в своих программах и документации.
Вам не нужно спрашивать разрешения на использование, если только вы не соби-
раетесь скопировать значительный фрагмент кода. Например, написание програм-
мы с использованием нескольких фрагментов кода из этой книги не требует раз-
решения. Ответ на чей-либо вопрос с упоминанием этой книги и цитированием
примера кода не требует разрешения. Для вставки же значительного количества
кода из этой книги в документацию вашего продукта разрешение нужно. Мы не
требуем указания ссылки на источник, но будем очень благодарны за это.
Если вы считаете, что использование примеров кода требует разрешения, так как
отличается от приведенных ранее случаев, пожалуйста, напишите нам по адресу
permissions@oreilly.com.

Safari®%RRNV2QOLQH
Safari® Books Online — цифровая библиотека, работающая по запросу и предостав-
ляющая экспертное собрание книг и видео ведущих авторов мира по технологиям
и бизнесу.
Технологические специалисты, разработчики ПО, веб-дизайнеры и другие
профессионалы в области бизнеса и творчества используют Safari® Books Online
в качестве основного ресурса для исследований, решения проблем, обучения и сер-
тификационных тренировок.
Safari® Books Online предлагает ряд наборов продуктов и программ оплаты для
коммерческих организаций, государственных учреждений и частных лиц. Подпис-
чики в одной удобной для поиска базе данных получают доступ к тысячам книг,
обучающих видео и предпечатных рукописей от таких издателей, как O’Reilly Media,
Prentice Hall Professional, Addison-Wesley Professional, Microsoft Press, Sams, Que,
Peachpit Press, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann,
IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-
Hill, Jones & Bartlett, Course Technology и десятков других. Для получения более
подробной информации о Safari® Books Online вы можете посетить сайт.

 ’$’
Пожалуйста, адресуйте комментарии и вопросы, касающиеся этой книги, издателю:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
Выражения признательности

800-998-9938 (in the United States or Canada)


707-829-0515 (international or local)
707-829-0104 (fax)
У книги есть собственная веб-страница, где мы размещаем информацию о най-
денных опечатках, примеры и другую информацию. Она находится на http://oreil.ly/
learning-web-app.
Чтобы прокомментировать или задать технический вопрос об этой книге, по-
жалуйста, пишите на bookquestions@oreilly.com.
Более подробная информация об этой книге, курсах, конференциях и новостях
находится на нашем сайте по адресу http://www.oreilly.com.
Присоединяйтесь к нам на Facebook: http://facebook.com/oreilly.
Подписывайтесь на наш Twitter: http://twitter.com/oreillymedia.
Смотрите наш канал на YouTube: http://www.youtube.com/oreillymedia.

Œ#’$
Огромное спасибо ребятам из департамента информатики в Университете Се-
верной Каролины в Эшвилле за возможность провести этот курс дважды. И, ко-
нечно, спасибо студентам за терпение, поскольку материал постепенно эволюци-
онировал.
Спасибо редактору Мег Бланшетт за огромные усилия по удержанию меня
в нужном русле и, конечно, за бесконечное терпение из-за нарушения сроков. Я буду
скучать по нашей еженедельной электронной переписке!
Спасибо Саймону Сент-Лорену за множество советов в самом начале работы
и подсказки о том, что может подойти издательству O’Reilly.
Сильван Кавано и Марк Филипс внимательно прочитали каждую главу и вы-
сказывали множество очень полезных замечаний на протяжении всей работы.
Эмили Уотсон прочитала первые четыре главы и внесла очень много дельных
предложений. Майк Уилсон прочел последние четыре главы и дал бесценные тех-
нические советы. Я бесконечно благодарен всем вам и надеюсь когда-нибудь от-
платить вам тем же.
Боб Бенитс, Уилл Бласко, Дэвид Браун, Ребекка Дэвид, Андреа Фей, Эрик Хоуи,
Брюс Хауман, Джон Максвелл, Сюзан Рейсер, Бен Роузен и Вэл Скарлата прочи-
тали множество версий материала и дали много полезных рекомендаций. Я искрен-
не благодарен за потраченные вами время и усилия. Вы молодцы!
Несмотря на множество опытных рецензентов и друзей, которые просматрива-
ли материал, практически невозможно написать подобную книгу без каких-то
технических ошибок, опечаток и неверных решений. Я принимаю на себя полную
ответственность за все это.
ł

Создание веб-приложений — сложный процесс, включающий использование мно-


жества подвижных частей и интерактивных компонентов. Чтобы изучить, как это
делается, мы должны разобрать эти части на отдельные элементы и постараться
понять, как все они взаимодействуют друг с другом. А самое удивительное, что ком-
понент, с которым мы будем сталкиваться чаще всего, не содержит ни строчки кода!
В этой главе мы исследуем рабочий процесс разработки веб-приложений — по­
следовательность действий, которые выполняем для того, чтобы создать прило-
жение. При этом мы изучим основы работы с некоторыми инструментами, которые
делают этот процесс легко контролируемым и (в основном) безболезненным.
Эти инструменты — текстовый редактор, система контроля версий и браузер.
Мы не будем изучать все это глубоко, но исследуем достаточно для того, чтобы
оказаться готовыми начать веб-программирование на клиентской стороне. В главе 2
мы увидим рабочий процесс в действии, изучая HTML.
Если вы уже знакомы с этими инструментами, возможно, вам будет лучше просмот-
реть краткое изложение и примеры в конце этой главы, а затем двигаться дальше.

œ##
Инструмент, с которым вы будете сталкиваться чаще всего, — текстовый редактор.
Об этом необходимом элементе технологии порой незаслуженно забывают, но на
самом деле это самый важный инструмент в вашем рабочем чемоданчике, посколь-
ку это программа, с помощью которой вы взаимодействуете со своим кодом. По­
скольку код формирует конкретные элементы конструкции приложения, очень
важно, чтобы было как можно проще его создавать и редактировать. К тому же вы,
как правило, будете редактировать несколько файлов одновременно, так что очень
важно, чтобы текстовый редактор предоставлял возможность быстро и просто
перемещаться по файловой системе.
В прошлом вы, вероятно, тратили много времени на написание документов
и редактирование текста в программах наподобие Microsoft Word или Google
Docs. Это не лучшие примеры редакторов, о которых мы говорим. Эти редакторы
концентрируются скорее на возможностях форматирования текста, чем на мак-
симальной простоте его редактирования. Редактор, который будем использовать
Текстовые редакторы

мы, обладает очень скромными возможностями форматирования, но зато имеет


множество функций, позволяющих эффективно манипулировать текстом.
На другом конце спектра находятся интегрированные среды разработки
(Integrated Development Environments, IDE), такие как Eclipse, Visual Studio и XCode.
Эти продукты, как правило, включают в себя функции простого управления кодом,
но у них есть также очень много инструментов, важных при промышленной разра-
ботке ПО. Мы не будем рассматривать такие инструменты в этой книге, так как
стремимся к простоте.
Так какие же текстовые редакторы будем рассматривать? В современной раз-
работке веб-приложений обычно используются две основные категории. Пер-
вая — редакторы с графическим пользовательским интерфейсом (Graphical User
Interface, GUI). Поскольку я предполагаю, что вы знакомы с основами программи-
рования и информатики, то, скорее всего, у вас есть опыт работы с настольными
приложениями с графическим пользовательским интерфейсом. Следовательно,
эти редакторы будут для вас относительно удобными. Они отлично подходят для
работы с мышью в качестве устройства ввода и имеют понятные меню, которые
позволяют взаимодействовать с файловой системой как с любой другой програм-
мой. Примерами текстовых редакторов с графическим пользовательским интер-
фейсом могут послужить TextMate, Sublime Text и Coda.
Другая категория текстовых редакторов — терминальные редакторы (текстовые
редакторы, работающие из командной строки). Эти редакторы разработаны еще до
того, как были изобретены графический пользовательский интерфейс или мыши,
поэтому их изучение — весьма нетривиальная задача для людей, привыкших к вза-
имодействию с компьютером посредством интерфейса и мыши. Однако эти редак-
торы могут быть весьма эффективными, если вы уделите их изучению достаточно
времени. Наиболее популярные редакторы из этой категории — Emacs (рис. 1.1)
и Vim (рис. 1.2).

Рис. 1.1. Документ HTML, открытый в Emacs


Глава 1. Рабочий процесс

Рис. 1.2. Документ HTML, открытый в Vim

В этой книге мы будем пользоваться редактором с графическим интерфейсом,


который называется Sublime Text, но я все же очень рекомендую вам поработать
с Emacs или Vim. Если вы продолжите деятельность в сфере разработки веб-при-
ложений, весьма возможно, что будете сталкиваться с их использованием очень
часто.

ß6XEOLPH7H[W
Sublime Text (или для краткости Sublime) — популярный многофункциональный
текстовый редактор, идеально подходящий для веб-разработки. Кроме этого, у него
есть преимущество кросс-платформенности, то есть он работает одинаково эффек-
тивно с Windows, Mac OS и Linux. Это платная программа, но вы можете скачать
бесплатную версию и использовать ее сколько захотите. Если этот редактор вам
понравится и вы будете пользоваться им постоянно, рекомендую оплатить лицен-
зию.
Чтобы установить Sublime, зайдите на http://www.sublimetext.com и щелкните на
ссылке Download вверху страницы. Там вы найдете программы установки для всех
основных платформ. Хотя (на момент написания книги) Sublime Text 3 находится
на бета-тестировании, я рекомендую попробовать именно его. Я использовал его
для всех примеров и скриншотов из этой книги.

#6XEOLPH7H[W
После установки и запуска Sublime Text вы увидите экран примерно такого вида,
как показано на рис. 1.3.
Текстовые редакторы

Рис. 1.3. Sublime Text, запущенный в первый раз

Вероятно, в первую очередь вы захотите создать новый файл. Это можно сделать
с помощью меню FileNew. Вы можете также нажать Ctrl+N в Windows и Linux либо
использовать Command+N в Mac OS. А сейчас наберите в редакторе Hello, World!
Редактор будет выглядеть так, как показано на рис. 1.4.
Вы можете изменить внешний вид окружения Sublime, зайдя в меню Preferences
Color Scheme. Попробуйте разные цветовые схемы и найдите ту, которая будет самой
подходящей для ваших глаз. На самом деле будет полезно потратить какое-то вре-
мя на исследование цветовых вариантов, поскольку вы будете проводить за работой
с редактором много часов. Можете также изменить размер шрифта через подменю
Font на вкладке Preferences, чтобы читать текст было еще удобнее.

Рис. 1.4. Sublime после открытия нового файла и введения Hello, world!
Глава 1. Рабочий процесс

Вы, наверное, заметили, что Sublime изменил название вкладки с untitled на Hello,
world!, как вы набрали. Когда будете сохранять файл, в качестве имени по умолча-
нию будет предложено название вкладки, но вы, возможно, захотите изменить его
так, чтобы он не содержал пробелов. После сохранения под другим именем назва-
ние вкладки изменится на действительное имя файла. Отмечу, что после того как
вы затем проделаете с содержимым файла что-то еще, вы увидите, что знак «×»
справа от названия вкладки изменился на кружочек — это значит, что здесь есть
несохраненные изменения.
После того как вы измените цветовую схему и сохраните файл под именем hello,
редактор будет выглядеть так, как показано на рис. 1.5.

Рис. 1.5. Sublime после изменения цветовой схемы на Solarized (light) и сохранения файла
под именем hello

Поскольку мы будем работать с командной строкой, лучше всего избегать пробелов


и специальных символов в именах файлов. Можно сохранять файлы с использованием
нижнего подчеркивания (_) вместо пробела, но постарайтесь не использовать никаких
других нецифровых и неалфавитных символов.

Мы будем проводить много времени, редактируя код в Sublime, поэтому жела-


тельно иметь уверенность в том, что вы время от времени сохраняете свои измене-
ния. Поскольку я ожидаю, что у вас есть небольшой опыт кодирования, думаю, вы
уже знакомы с процессом edit — save — edit. Существует и необходимый смежный
процесс, с которым начинающие программисты могли не сталкиваться. Он назы-
вается контролем версий.

$
Представьте, что вы написали с помощью текстового редактора большой фрагмент
художественной повести. Вы периодически сохраняете работу, чтобы случайно не
Контроль версий

потерять ее. И вот однажды, достигнув важного момента в повествовании, вы по-


нимаете, что значительной части истории главного героя не хватает. И решаете
дополнить ее некоторыми деталями, вернувшись куда-нибудь в начало истории.
Вы возвращаетесь к началу и понимаете, что существует два варианта развития
событий для этого персонажа. Поскольку повествование еще не закончено, реша-
ете написать черновики каждого из вариантов и посмотреть, к чему они приведут.
Поэтому вы копируете файл в два различных места, один из вариантов называете
«История А», другой — «История Б», а затем записываете в каждый из файлов
различные варианты развития событий.
Можете мне не верить, но компьютерные программы создаются похожим образом
гораздо чаще, чем романы. Фактически, как вы убедитесь, значительная часть времени
тратится на что-то вроде исследовательского кодирования. Это значит, что вы разбира-
етесь, что нужно сделать, и пытаетесь заставить функциональность как-то работать,
прежде чем действительно приступите к ее кодированию. Иногда фаза исследователь-
ского кодирования может привести к необходимости внесения изменений, затрагива-
ющих множество строк в различных кодовых файлах приложения. Даже начинающие
программисты поймут это скорее рано, чем поздно и в конце концов придут к решению,
подобному описанному. Например, начинающие могут скопировать рабочую папку
с кодом в другую, немного изменить имя и продолжить работу. Если окажется, что
допущена ошибка, они всегда могут вернуться к предыдущей копии.
Это весьма приблизительный подход к контролю версий. Контроль версий — это
процесс, который позволяет вам создавать в коде помеченные контрольные точки,
к которым вы всегда можете обратиться (или даже вернуться), если будет необхо-
димо. Кроме того, контроль версий — незаменимый инструмент для взаимодей­
ствия с другими разработчиками. Мы не будем уделять данному аспекту много
внимания в этой книге, но постарайтесь запомнить это.
Существует много профессиональных инструментов для контроля версий, и все
они имеют собственный набор функций и особенностей. В качестве общих приме-
ров можно привести Subversion, Mercurial, Perforce, CVS. В сообществе веб-разра-
ботчиков наибольшую популярность приобрела система контроля версий под на-
званием Git.

ß*LW
У Git есть очень простые инсталляторы как для Windows, так и для Mac OS.
Для Windows мы будем использовать проект msysgit, который доступен на GitHub
(рис. 1.6). Инсталляторы доступны также на Google Code и связаны ссылками со
страницей GitHub. Как только вы скачаете инсталлятор, щелкните на нем и сле-
дуйте инструкциям, чтобы установить Git в свою систему.
Для Mac OS я предпочитаю использовать инсталлятор Git for OS X Istaller
(рис. 1.7). Вы просто скачиваете образ диска в архиве, монтируете его, а затем де-
лаете двойной щелчок на инсталляторе. На момент написания книги инсталлятор
сообщал, что версия предназначена для Mac OS Snow Leopard (10.5), но она впол-
не корректно работала и на моей системе Mountain Lion (10.8).
Глава 1. Рабочий процесс

Рис. 1.6. Домашняя страница msysgit

Рис. 1.7. Домашняя страница Git for OS X

Если вы пользуетесь Linux, то можете установить Git через систему управления


пакетами.

## 81,;
Для Git есть графические интерфейсы, но гораздо более эффективно научиться
работать с ним с помощью командной строки. Перед тем как вы начнете учиться
Контроль версий

этому, придется разобраться, как работать с файловой системой, используя неко-


торые основные команды UNIX.
Как я уже упоминал, я предполагаю, что вы немного знакомы с информатикой
и программированием, но в основном взаимодействовали с окружением с графи-
ческим пользовательским интерфейсом. Это значит, что вы привыкли использовать
настольное окружение для работы с файлами и папками, находящимися на вашем
компьютере. Скорее всего, вы делаете это через менеджер файловой системы, на-
пример Finder в Mac OS или проводник в Windows.
Навигация в файловой системе с использованием командной строки очень
похожа на аналогичный процес, осуществляемый с помощью системного файло-
вого менеджера. Мы так же работаем с файлами, которые организованы в папки
(каталоги). Вы можете выполнять те же самые операции, которые делаете с помо-
щью файлового менеджера: перемещать файлы в каталоги или извлекать их отту-
да, просматривать список файлов, находящихся в папке, и даже открывать и редак-
тировать файлы, если знакомы с Emacs или Vim. Отличия состоят в том, что вы не
получаете постоянной обратной связи с помощью графического пользовательско-
го интерфейса и не можете взаимодействовать с мышью.
Если вы работаете в Windows, то будете использовать командную строку Git
Bash, которую установили вместе с msysgit project, как описано в предыдущем
разделе. Git Bash — это программа, симулирующая командную строку UNIX в Windows
и дающая вам доступ к командам Git. Запустить командную строку Git Bash мож-
но через меню Пуск. Если вы работаете с Mac OS, то будете использовать програм-
му Terminal, которую можете найти в папке Utilities внутри каталога Applications. Если
вы используете Linux, все может зависеть от конкретной конфигурации, которую
вы предпочитаете, но, как правило, вполне успешно можно применять легкодо-
ступную программу Terminal, которая находится в ваших приложениях. Терми-
нальное окно по умолчанию в Mac OS показано на рис. 1.8.

Рис. 1.8. Терминальное окно по умолчанию в Mac OS


Глава 1. Рабочий процесс

Открыв командную строку, вы видите приветственное сообщение. Оно может


выглядеть по-разному в зависимости от того, работаете вы в Mac OS или Windows,
но, как правило, содержит какую-либо информацию о вашем рабочем окружении.
В частности, оно может включать текущую папку или ваше пользовательское имя.
Мое сообщение в Mac OS выглядит вот так:
Last login: Tue May 14 15:23:59 on ttys002
hostname $ _

º’"
Очень важно помнить, что, работая в командной строке, вы всегда выполняете
команды из какой-либо папки. Первый вопрос, который вы должны задать себе,
попав в интерфейс командной строки: «В какой папке я нахожусь?» Получить
ответ можно двумя способами. Первый — использовать команду pwd, что означает
print working directory («вывести рабочую директорию» (то есть папку)). Вывод
будет выглядеть примерно так:
hostname $ pwd
/Users/semmy

Хотя иногда я и использую pwd, но обычно предпочитаю команду ls, которую


можно расшифровать как list the content of the current directory («перечисли со-
держимое текущей директории»). Это дает мне больше визуальных подсказок о том,
где я нахожусь. В Mac OS вывод будет выглядеть примерно так:
hostname $ ls
Desktop Downloads Movies Pictures
Documents Library Music

Таким образом, команда ls аналогична открытию папки в Finder или Проводни-


ка из вашего домашнего каталога. Результат выполнения команды показал мне, что
я нахожусь в своем домашнем каталоге, поскольку вижу все подкаталоги, выведен-
ные на экран. Если я не узнаю подкаталоги, хранящиеся в этой папке, то могу ис-
пользовать pwd для получения дальнейшей информации.

Следующее действие, которое вам понадобится, — это переход в следующую папку


из той, где вы находитесь в настоящий момент. Если вы работаете с файловым
менеджером с графическим пользовательским интерфейсом, переход осуществля-
ется двойным щелчком на нужной папке.
Выполнить его из командной строки ничуть не сложнее: просто запомните на-
звание команды — cd, что значит change directory («изменить директорию»). Если,
например, вы хотите перейти в вашу папку Documents, просто введите:
hostname $ cd Documents

Сейчас, если вы хотите получить визуальную обратную связь, можете исполь-


зовать ls:
Контроль версий

hostname $ ls
Projects

Мы видим один подкаталог в каталоге Documents, и этот подкаталог называется


Projects (Проекты). Понятно, что если вы его предварительно не создали, то каталог
Projects не отобразится. Вы также можете увидеть другие файлы или папки, создан-
ные ранее в папке Documents. А сейчас, после перехода в новую папку, выполним
команду pwd, чтобы уточнить наше текущее положение:
hostname $ pwd
/Users/semmy/Documents

Что произойдет, если вы хотите вернуться обратно в домашнюю папку? В фай-


ловом менеджере с графическим интерфейсом обычно есть кнопка Назад (Back),
с помощью которой вы можете вернуться в предыдущую папку. В консоли, конеч-
но, такой кнопки нет. Но вы можете снова использовать команду cd с небольшим
изменением — двумя точками (..) вместо названия каталога, чтобы вернуться в пре-
дыдущее место расположения:
hostname $ cd ..
hostname $ pwd
/Users/semmy
hostname $ ls
Desktop Downloads Movies Pictures
Documents Library Music

ø
И наконец, вам может понадобиться создать новую папку для хранения своих
проектов, которые вы будете создавать с помощью этой книги. Для этого исполь-
зуйте команду mkdir, которая означает make directory («сделать директорию»):
hostname $ ls
Desktop Downloads Movies Pictures
Documents Library Music
hostname $ mkdir Projects
hostname $ ls
Desktop Downloads Movies Pictures
Documents Library Music Projects
hostname $ cd Projects
hostname $ ls
hostname $ pwd
/Users/semmy/Projects

При этом взаимодействии с командной строкой вы сначала смотрите содержи-


мое текущей папки, чтобы удостовериться, где находитесь, с помощью команды
ls. После этого используете команду mkdir для создания каталога Projects. Затем
используете ls, чтобы убедиться, что каталог создан. После этого вводите cd, что-
бы попасть в папку Projects, а потом — ls, чтобы вывести ее содержимое. Только
что созданная папка, разумеется, пуста, поэтому в результате выполнения коман-
Глава 1. Рабочий процесс

ды ls ничего выведено не будет. И наконец, последнее по очереди, но не по важ-


ности — вы используете pwd, чтобы убедиться, что находитесь именно в папке
Projects.
Этих четырех основных команд UNIX вам будет вполне достаточно для начала,
но вы изучите больше по мере нашего продвижения вперед. В конце главы я раз-
местил удобную таблицу с перечнем и описанием этих команд. Будет очень хорошо,
если вы постараетесь их запомнить.

## ##
Веб-разработка (и программирование в целом) — очень абстрактная форма твор-
чества. В целом это означает, что для эффективного и рационального труда вы
должны развивать абстрактное мышление. Во многом эта способность требуется
для быстрого создания ментальных моделей новых идей и структур. Одна из луч-
ших ментальных моделей, которая применяется во множестве разных ситуаций, —
древовидная диаграмма.
Древовидная диаграмма — это простой способ визуализации любого типа
иерархической структуры. Поскольку файловая система в UNIX является иерар-
хической структурой, будет естественно начать упражнения в ментальной визуа-
лизации с нее. Например, представьте себе, что папка Home (Домашняя) содержит
три другие папки: Documents (Мои документы), Pictures (Мои рисунки) и Music (Моя
музыка). Внутри папки Pictures находятся пять картинок. Внутри каталога Documents
содержится другой каталог — Projects (Проекты) (рис. 1.9).

Рис. 1.9. Древовидная диаграмма, представляющая файловую иерархию


Контроль версий

Постарайтесь держать эту ментальную модель в голове, перемещаясь по фай-


ловой системе. На самом деле я рекомендовал бы вам добавить звездочку (или
аналогичный символ), которая означает текущую папку, и перемещать ее одновре-
менно с движением по файловой системе.
Вообще говоря, если вы примените древовидную диаграмму к любой иерархи-
ческой структуре, то, скорее всего, вам будет гораздо легче понять и анализировать
ее. Поскольку способность стать хорошим программистом в значительной степени
зависит от умения быстро строить ментальные модели, старайтесь практиковаться
в применении древовидной диаграммы к иерархическим системам реального мира
почаще — всегда, когда это имеет смысл. Мы вернемся к ним еще несколько раз
в тексте книги.

#*LW
Сейчас, когда мы умеем передвигаться по файловой системе с помощью командной
строки, пора научиться управлять версиями нашего проекта с помощью Git.

ı*LW
Как я уже говорил, Git был разработан в первую очередь для масштабного взаимо-
действия между большим количеством программистов. Хотя мы собираемся ис-
пользовать его только для собственных проектов, нужно настроить его так, чтобы
можно было отслеживать изменения с помощью некоторой дополнительной инфор-
мации, например имени и адреса электронной почты. Откройте командную строку
и наберите следующие команды (изменив, разумеется, мои имя и адрес на свои):
hostname $ git config --global user.name "Semmy Purewal"
hostname $ git config --global user.email semmy@semmy.me
Для нашей системы придется сделать это всего один раз! Другими словами, вам
не нужно повторять эти действия каждый раз, когда вы хотите создать проект,
используя Git.
А сейчас мы готовы начать отслеживание проекта с использованием Git. Начнем
с перехода в наш каталог Projects, если еще не находимся там:
hostname $ pwd
/Users/semmy
hostname $ cd Projects
hostname $ pwd
/Users/semmy/Projects
Затем создадим каталог под названием Chapter1 (Глава 1)1 и просмотрим содер-
жимое, чтобы убедиться, что он тут. Затем перейдем в новую папку:

1
При работе с Git возможны трудности с выводом кириллических символов в именах
файлов и папок. Все сообщения, где есть русские символы, преобразуются в кодировку
UTF-8 по latin1. Решение проблемы описано на http://habrahabr.ru/post/74839/, но на
начальном этапе можно просто без крайней необходимости не использовать кириллические
и другие специальные символы в названиях файлов и папок. — Примеч. пер.
Глава 1. Рабочий процесс

hostname $ mkdir Chapter1


hostname $ ls
Chapter1
hostname $ cd Chapter1
hostname $ pwd
/Users/semmy/Projects/Chapter1

ø!*LW
Сейчас мы можем установить контроль версий над папкой Chapter1, создав храни-
лище в Git с помощью команды git init. Git ответит нам, что создано новое пустое
хранилище:
hostname $ pwd
/Users/semmy/Projects/Chapter1
hostname $ git init
Initialized empty Git repository in /Users/semmy/Projects/Chapter1/.git/1

А сейчас снова введите команду ls, чтобы увидеть файлы, которые Git создал
в папке, и вы увидите, что там ничего нет! Это не совсем верно — здесь находится
папка .git, но мы не можем ее увидеть, так как файлы, помеченные впереди точкой
(.), считаются скрытыми. Чтобы решить эту проблему, можно использовать коман-
ду ls c включенным флагом –а (all — «все»), набрав следующее:
hostname $ ls -a
. .. .git

Таким образом выводится все содержимое папки, включая файлы, помеченные


впереди точкой. Вы даже видите вывод своей текущей папки (это одинарная точка)
и ее родительской папки (две точки).
Если вам интересно, вы можете вывести и содержимое каталога .git, увидев таким
образом файловую систему, которую Git подготовил для вас:
hostname $ ls .git
HEAD config hooks objects
branches description info refs

Нам не придется ничего делать в этой папке, так что сейчас мы можем смело ее
проигнорировать. Но со скрытыми файлами еще встретимся, так что постарайтесь
запомнить полезный флаг –a для команды ls.

!
Откроем Sublime Text (если он все еще открыт со времени чтения предыдущего
раздела, закройте его и откройте снова). Затем откройте папку, версии в которой
мы будем контролировать. Чтобы сделать это, просто выбираем папку в диалоговом
окне Open программы Sublime вместо конкретного файла. Когда вы открываете

1
Системное сообщение: «Создано пустое хранилище Git в /Users/semmy/Projects/Chap-
ter1/.git». — Примеч. пер.
Контроль версий

целую папку, файловая навигационная панель появляется с левой стороны окна


редактора — это выглядит примерно как на рис. 1.10.

Рис. 1.10. Sublime с открытой папкой Chapter1

Чтобы создать новый файл в папке Chapter1, щелкните правой кнопкой (или
выполните Command-щелчок в Mac OS) на названии Chapter1 в файловой навига-
ционной панели, а затем выберите New File из контекстного меню. Как и ранее,
будет создан новый файл, но, когда вы сохраните его, по умолчанию будет исполь-
зована папка Chapter1. Назовем файл index.html.
После того как файл получил имя, сделайте на нем двойной щелчок и добавьте
строку Hello, World! в верхнюю часть файла (рис. 1.11).

Рис. 1.11. Sublime после редактирования и сохранения файла index.html


Глава 1. Рабочий процесс

Посмотрим, что произошло с хранилищем Git. Вернитесь к командной строке


и убедитесь, что находитесь в нужном каталоге:
hostname $ pwd
/Users/semmy/Projects/Chapter1
hostname $ ls
index.html

А сейчас наберите git status и посмотрите на примерно такой ответ:


hostname $ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# index.html1

Здесь довольно много информации! Больше всего нас интересует блок, поме-
ченный Untracked files (Неотслеживаемые файлы). Здесь перечислены файлы,
которые находятся в рабочей папке, но пока не включены в контроль версий.
Мы видим, что файл index.html здесь и он может быть зафиксирован (committed)
в хранилище Git.

ı ##
Мы заинтересованы в отслеживании изменений в файле index.html. Чтобы иметь
возможность делать это, последуем инструкциям, которые выдал Git, и добавим
файл в хранилище, используя команду git add:
hostname $ git add index.html

Обратите внимание на то, что в этом случае мы не получаем никакого ответа.


Так и должно быть. Можем убедиться, что команда сработала, набрав git status
еще раз:
hostname $ git status
# On branch master
#

1
Системное сообщение: «От бранч-мастера. Начальный коммит. Неотслеживаемые
файлы (используйте "git add <file>..." для добавления их в список для фиксирования
изменений): index.html». — Примеч. пер.
2
Коммит (commit) — очередная версия отслеживаемого файла или нескольких файлов,
отправленная в систему контроля версий. Само по себе изменение файла с кодом,
например, коммитом не является, а становится таковым только после загрузки в систему
контроля версий. Один или несколько коммитов могут составлять билд (сборку) —
очередное обновление версии программы. Например, в новый билд могут входить три
коммита: один с добавлением новой функциональности, другой — с изменением внешнего
вида интерфейса, а третий — с исправлением дефекта. — Примеч. пер.
Контроль версий

# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: index.html1
#

Это и есть нужная нам обратная связь. Обратите внимание на то, что index.html
теперь указан после заголовка Changes to be commited (Изменения, которые должны
быть зафиксированы).
После добавления новых файлов в хранилище мы хотим зафиксировать его
начальное состояние. Для этого используем команду git commit с флагом –m и смыс-
ловым сообщением о том, что изменилось со времени последнего коммита. Чаще
всего начальный коммит будет выглядеть так:
hostname $ git commit -m "Initial commit"
[master (root-commit) 147deb5] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 index.html2

Таким образом мы получаем мгновенный снимок состояния нашего проекта


в определенный момент. Мы всегда можем откатить состояние системы к нему
позднее, если решим, что где-то пошли неверным путем. Сейчас команда git status
не покажет нам файла index.html, так как он уже отслеживается, а никаких измене-
ний внесено не было. Если мы ничего не изменяли со времени последнего комми-
та, у нас сейчас, как говорят, актуальный рабочий каталог:
hostname $ git status
# On branch master
nothing to commit (working directory clean)3

Легко забыть включить флаг –m и сообщение для коммита в процессе заливки измене-
ний в систему контроля версий. Если это произойдет, вы, скорее всего, обнаружите себя
внутри текстового редактора Vim (который обычно по умолчанию является системным
редактором). Если это так, вы можете покинуть его нажатием двоеточия (:), а затем
ввести q! и нажать Enter, чтобы выйти.

Затем внесем в index.html небольшие изменения. Добавим вторую строку, ко-


торая говорит: Goodbye, World! («Прощай, мир!»). Сделайте это и сохраните файл,

1
Системное сообщение: «От бранч-мастера. Начальный коммит. Изменения для коммита:
(используйте "git rm --cached <file>..." для отката). Новый файл: index.html». — Примеч. пер.
2
Системное сообщение: «[master (root-commit) 147deb5] Начальный коммит. 1 файл
изменен, 1 добавлен (+). Создан модуль 100644 index.html». — Примеч. пер.
3
Системное сообщение: «От бранч-мастера: нет изменений для коммита (рабочая
директория актуальна)». — Примеч. пер.
Глава 1. Рабочий процесс

использовав соответствующее клавиатурное сокращение. А сейчас посмотрим, как


git status отреагирует на наши действия:
hostname $ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: index.html1
#
no changes added to commit (use "git add" and/or "git commit -a")

Обратите внимание: Git предупреждает нас, что index.html был изменен, но не


добавлен в следующий коммит. Чтобы добавить наши изменения в хранилище, мы
должны выполнить команду git add для измененного файла, а затем — git commit,
чтобы зафиксировать изменения в системе контроля версий. Мы можем убедиться
в том, что добавление прошло успешно, набрав git status перед тем, как выполнять
фиксацию изменений в системе контроля версий. Наши действия будут выглядеть
примерно так:
hostname $ git add index.html
hostname $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
hostname $ git commit -m "Add second line to index.html"
[master 1c808e2] Add second line to index.html
1 file changed, 1 insertion(+)2

$&!
Мы сделали целых два коммита проекта и можем откатить систему к их состояни-
ям в любое время. В разделе «Больше теории и практики» далее я привожу ссылку,
которая покажет вам, как откатить предыдущий коммит обратно и начать кодиро-
вание из этой точки. А сейчас изучим еще одну очень полезную команду. Мы можем
посмотреть историю коммитов, используя git log:

1
Системное сообщение: «От бранч-мастера: Изменения не зафиксированы в коммит.
(используйте git add <file> для обновления списка файлов для коммита) (используйте
"git checkout -- <file>..." для отмены изменений в рабочей директории) Изменен: index.
html». — Примеч. пер.
2
Системное сообщение: «От бранч-мастера: Изменения для коммита: (используйте
"git rm --cached <file>..." для отката). Изменен: index.html. hostname $ git commit
–m Добавлена вторая строка в index.html. 1 файл изменен, 1 добавление (+)». —
Примеч. пер.
Контроль версий

hostname $ git log


commit 1c808e2752d824d815929cb7c170a04267416c04
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 10:36:47 2013 -0400
Add second line to index.html
commit 147deb5dbb3c935525f351a1154b35cb5b2af824
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 10:35:43 2013 -0400
Initial commit
Очень полезно запомнить эти четыре команды Git, как и четыре команды UNIX,
рассмотренные в предыдущем разделе. Удобная диаграмма в разделе «Подведем
итоги» иллюстрирует эти команды.

ø#
На всякий случай, чтобы избежать неясностей, я хотел бы подробно остановиться
на разнице между сохранением файла в текстовом редакторе и фиксированием
изменений в системе контроля версий. Когда вы сохраняете файл, по сути, вы пе-
реписываете его на диск компьютера. Это значит, что вы больше не получите до-
ступа к старой версии файла, если только редактор не предоставляет возможности
построения истории ревизий.
Фиксирование же в хранилище Git позволяет вам отслеживать все изменения,
которые вы сделали с последнего раза, когда добавляли новую версию файла. Это
значит, что вы всегда можете вернуться к предыдущей версии, если обнаружили,
что сделали трудноисправимую ошибку в текущей версии файла.
В данный момент у вас, вероятно, сложилось впечатление, что Git сохраняет код
как линейную последовательность коммитов. Сейчас это действительно так: мы
изучили ту часть Git, которая позволяет создать такое хранилище, где один коммит
следует строго за другим коммитом. Мы можем назвать первый коммит родитель-
ским, а второй — дочерним. Хранилище Git с четырьмя коммитами показано на
рис. 1.12.
Следует отметить, однако, что коммит, по сути, — это серия инструкций по
обновлению вашего проекта до следующей версии. Другими словами, Git на самом
деле вовсе не сохраняет каждый раз содержимое каталога полностью, как если бы
вы скопировали одну папку на компьютере в другую. Вместо этого он просто фик-
сирует, что именно должно быть изменено: например, коммит может хранить ин-
формацию вроде «добавь строку с текстом Goodbye, World!» вместо сохранения
всего файла. Так что лучше представлять себе хранилище Git как серию инструк-
ций. Вот почему мы пишем сообщения для коммитов в форме императива — вы
можете представлять себе коммит как последовательность указаний для перевода
проекта из одного состояния в другое.
Почему все это так важно? Дело в том, что хранилище Git может иметь гораздо
более сложную структуру. Коммит может иметь более одного «ребенка» и факти-
чески более одного «родителя». На рис. 1.13 показан пример более сложного
хранилища Git, где иллюстрируются оба этих случая.
Глава 1. Рабочий процесс

Рис. 1.12. Хранилище Git с четырьмя коммитами

Рис. 1.13. Более сложное хранилище Git

Хотя мы еще не изучили команд Git для создания структуры наподобие этой,
вам придется поближе познакомиться с ними, если вы захотите продолжить путе-
шествие по миру веб-разработки. Я уверен, вы сделали вывод о необходимости
составления визуальной схемы хранилища Git, чтобы не запутаться при возраста-
нии сложности.

Ø#
Последний инструмент, с которым мы будем регулярно сталкиваться, — браузер.
Прежде чем изучать создание веб-приложений, которые будут запускаться в браузере,
Подведем итоги

следует узнать, как эффективно использовать его в качестве инструмента разра-


ботки, а не только как окно в Интернет.
Несколько лучших браузеров — Firefox, Safari и Chrome. Я бы рекомендовал
вам приобретать опыт использования инструментов для разработки, доступных во
всех трех. Но поначалу в целях простоты и синхронизации остановимся на Google
Chrome.
Установка Chrome. Работаете ли вы в Windows, Mac OS или Linux, вы можете
установить Google Chrome, просто зайдя на его веб-страницу. Процесс установки,
конечно же, будет различаться, но инструкции очень просты. Как только вы уста-
новите Chrome и запустите его в первый раз, он будет выглядеть так, как показано
на рис. 1.14.

Рис. 1.14. Окно Chrome по умолчанию

Один из самых важных аспектов разработки веб-приложения — освоение рацио-


нального и эффективного рабочего процесса. Современный рабочий процесс вклю-
чает в себя три важных инструмента: текстовый редактор, систему контроля версий
и браузер. Sublime Text — популярный мультиплатформенный текстовый редактор,
который отлично подходит для редактирования исходного кода. Git — широко
используемая система контроля версий с управлением из командной строки.
Chrome — прекрасный браузер для веб-разработки.
Прежде чем двигаться дальше, вы должны установить все эти инструменты на
свой компьютер. Вы должны также запомнить команды из табл. 1.1 и 1.2, которые
позволят вам передвигаться по файловой системе и взаимодействовать с Git из
командной строки.
Глава 1. Рабочий процесс

Таблица 1.1. Команды UNIX

pwd Вывод на экран текущей папки


ls Вывод на экран содержимого текущей папки
ls -a Вывод на экран содержимого текущей папки, включая скрытые файлы
cd [dir] Перейти в каталог [dir]
mkdir [dir] Создать каталог [dir]

Таблица 1.2. Команды Git

git init Инициализация хранилища


git status Вывод на экран статуса хранилища
git add [file(s)] Добавить файл(-ы) [file(s)] для фиксации в следующем коммите
git commit –m [msg] Зафиксировать в системе контроля версий добавленные файлы с со-
общением [msg — текст сообщения]
git log Показать историю коммитов

Ø$  

В сфере обучения слово «заучивание» обычно имеет негативную окраску. Я бы


сказал, что это несправедливо, особенно в отношении компьютерного программи-
рования. Если вы настраиваетесь так: «Ага, буду знать, где искать это, если пона-
добится», то проведете больше времени в поисках базовых инструментов, чем
концентрируясь на интересных задачах. Представьте, например, насколько труднее
были бы задачки на деление, если бы вы не помнили таблицу умножения на-
изусть!
Именно поэтому я собираюсь включить в конец первых нескольких глав раздел
«Заучивание», который будет содержать основные понятия, которые вы должны
хорошенько заучить, прежде чем двигаться дальше. Для этой главы все важное
относится к командным строкам Git и UNIX. Просто повторяйте снова и снова
следующие действия, пока не сможете выполнить их, не подглядывая в докумен-
тацию.
1. Создайте новую папку с помощью командной строки.
2. Войдите в эту папку с помощью командной строки.
3. Создайте текстовый файл в текстовом редакторе и сохраните его как index.html
в новой папке.
4. Инициализируйте хранилище Git из командной строки.
5. Добавьте и зафиксируйте этот файл в хранилище с помощью командной строки.
Каков же лучший способ запомнить эту последовательность задач? Все очень
просто: делайте их снова и снова. На этих действиях основано много материала
Больше теории и практики

следующих нескольких глав, поэтому очень важно хорошенько выучить их


сейчас.

6XEOLPH7H[W
Как я уже упоминал ранее, вы будете проводить много времени, работая с текстовым
редактором, так что весьма неплохо будет как следует изучить его. На веб-сайте
Sublime есть отличная страница поддержки, где находятся ссылки на документацию
и видео, демонстрирующие продвинутые возможности редактора. Я рекомендую вам
исследовать эту страницу и немного улучшить свои навыки работы с Sublime.

(PDFV9LP
Почти каждый веб-разработчик хоть раз да столкнется с необходимостью редакти-
ровать файл прямо на удаленном сервере. Это значит, что вы должны уметь исполь-
зовать текстовый редактор, в котором отсутствует графический пользовательский
интерфейс. Emacs и Vim — невероятно мощные редакторы, поднимающие эффек-
тивность процесса разработки до невиданных высот, но обучение работе с ними
может быть крутовато для новичков. Если вы найдете время, постарайтесь изучить
основы работы в обоих редакторах, хотя, кажется, популярность Vim у веб-разра-
ботчиков в последнее время возрастает (ладно, признаюсь: я пользуюсь Emacs).
На домашней странице GNU есть отличнейший обзор Emacs, включающий
учебный материал для новичков. Издательство O’Reilly выпустило несколько книг
по Emacs и Vim, включая Learning the vi and Vim Editors («Изучение vi и редакторов
Vim») авторов Арнольда Роббинса, Эльберта Ханная и Линды Лэмб, а также
Learning GNU Emacs («Изучение GNU Emacs») авторов Дебры Камерон, Джеймса
Элиотта, Марка Лойя, Эрика Рэймонда и Билла Розенблатта.
Вам будет очень полезно изучить, как выполняются в каждом из редакторов
следующие действия:
 открытие редактора и выход из него;
 открытие, редактирование и сохранение существующего файла;
 открытие нескольких файлов одновременно;
 создание нового файла и его сохранение;
 поиск файла по заданному слову или фразе;
 вырезание фрагментов текста из одного файла и вставка в другой.
Если вы уделите этому время, то постепенно определитесь, с каким редактором
хотите продолжать работу и обучение.

’81,;
Чтобы стать мастером работы с командной строкой UNIX, понадобятся долгие
годы, но вы уже узнали достаточно, чтобы начать. По моему опыту, эффективным
Глава 1. Рабочий процесс

становится изучение нового по мере надобности, при решении специальных задач,


но существует еще несколько базовых команд, которые я применяю регулярно.
Используя поиск в Google, изучите материал о следующих общих командах: cp, mv,
rm, rmdir, cat и less. Все они пригодятся вам в разное время.

ß$ *LW
Git — невероятно мощный инструмент. Мы только слегка коснулись его возмож-
ностей. К счастью, Скотт Чейкон написал Pro Git (Apress, 2009) — прекрасную
книгу, где подробно рассматривается множество аспектов Git. В первых двух гла-
вах описывается несколько функций, которые помогут вам более эффективно
продвигаться в изучении материала этой книги, включая откат к версиям, предва-
рительно зафиксированным в хранилище.
В третьей главе книги Чейкона детально описана концепция бранчей. Бранчи
несколько выходят за рамки моей книги, но я упоминал о них ранее. Рекомендую
как следует изучить эту тему, так как возможность быстро и просто разветвить
хранилище — одна из лучших функций Git.

*LW+XE
GitHub — это онлайн-сервис, в котором находится ваше хранилище Git. Если вы
храните код открыто (open source), оно бесплатно. Если же хотите создать защи-
щенное хранилище Git, самый дешевый тарифный план — $7 в месяц. Я рекомен-
дую начать с бесплатного плана и исследовать хранилище Git на GitHub.
Страница помощи GitHub поможет вам настроить аккаунт GitHub и связать
его с вашим хранилищем Git. Там также находятся тонны полезной информации
и о Git, и о GitHub. Пользуйтесь ею, чтобы начать действовать.
ø

В следующих двух главах мы рассмотрим две важные для разработки клиентской


стороны темы: HTML и CSS. Поскольку рассмотреть их подробно в данной книге
вряд ли возможно, эти главы представляют собой скорее сборник конспектов, ко-
торые позволят вам изучить HTML и CSS в достаточной степени для того, чтобы
работать с примерами кода из книги. Раздел «Больше теории и практики» данной
главы поможет вам найти информацию для дальнейшего изучения.
Если вы уже знакомы с HTML и CSS, вполне возможно, свободно можете пе-
рейти к главе 4, которая начинается с изучения JavaScript на клиентской стороне.
Вы можете просмотреть главы и прочитать краткое содержание в конце, прежде
чем решить.

+70/
HTML, что значит Hyper Text Markup Language (язык гипертекстовой размет-
ки), — это технология, которая позволяет задавать структуру расположения визу-
альных элементов (иногда ее называют пользовательским интерфейсом) веб-при-
ложения. Что я подразумеваю под словом «структура»? Рассмотрим простой
пример.
Для начала с помощью командной строки создадим папку Chapter2 (Глава 2)
в папке Projects. Напомню, что для этого следует использовать команду mkdir. Затем
откроем эту папку в Sublime Text с помощью меню File или клавиш быстрого до-
ступа. Создайте новый файл под названием hello.html внутри этой папки. Набери-
те его содержимое точно так же, как показано далее:
<!doctype html>
<html>
<head>
<title>Мое первое веб-приложение</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Глава 2. Структура

œ
Во время набора вы, наверное, заметили, что в документе содержатся два типа
элементов. Один из них — обычный текст вроде «Мое первое веб-приложение» или
традиционное сообщение «Hello, World!». Другие элементы, например <html> или
<head>, заключенные в угловые скобки, называются тегами. Теги являются формой
метаданных, которые используются для применения нашей структуры к содержи-
мому страницы.
Запустите браузер и откройте в нем созданный файл, используя пункт Open File
(Открыть файл) в меню File (Файл). Вы увидите нечто похожее на рис. 2.1.

Рис. 2.1. Файл hello.html, открытый в Chrome

Очень полезно постараться выучить клавиатурные сокращения, так как это поможет
работать быстрее и рациональнее. Для открытия файла в Chrome вам нужна команда
Command+O, если вы работаете в Mac. В Windows эта же команда — Ctrl+O.

Как видите, теги не отображаются на странице, в отличие от остального текста.


Заголовок «Мое первое веб-приложение» отображается как название вкладки,
а содержимое «Hello, World!» появляется в основном пространстве окна.

œS!D#
А сейчас внесем небольшие изменения, добавив абзац текста Lorem ipsum — это
простой текстовый наполнитель, который мы можем заменить нужным нам
Привет, HTML!

текстом позднее. Вы можете скопировать и вставить его с вики-страницы Lorem


ipsum1:
<!doctype html>
<html>
<head>
<title>Мое первое веб-приложение</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.</p>
</body>
</html>

Сделав изменения в документе, мы должны их сохранить. Сейчас мы можем


вернуться в браузер и переоткрыть страницу, щелкнув на круглой стрелке рядом
с адресной строкой в Chrome. Вы увидите, что основное пространство браузера
обновилось (рис. 2.2).

Рис. 2.2. Модифицированный файл hello.html, открытый в Chrome

1
В Интернете много сервисов для генераторов текста lorem ipsum, в том числе на русском
языке. Используя кириллицу, при сохранении файла выставляйте кодировку UTF-8,
иначе символы могут отображаться некорректно. — Примеч. пер.
Глава 2. Структура

Вы можете обновлять страницы, используя сочетания клавиш Ctrl+R в Windows или


Command+R в Mac OS.

Так и будет выглядеть обычный рабочий процесс при редактировании веб-стра-


ниц. Мы открываем файл в текстовом редакторе, вносим небольшие изменения,
а затем обновляем браузер, чтобы увидеть их.

Комментарии — очень удобный способ делать пометки в HTML. Мы начинаем


комментарий с <!-- и заканчиваем его -->. Вот простой пример, созданный на ос-
нове материала предыдущего раздела:
<!doctype html>
<html>
<head>
<title>Пример комментария</title>
</head>
<body>
<!-- Это главный заголовок -->
<h1>Hello, World!</h1>
<!-- Это главный абзац текста -->
<p>Я — главный абзац, скорее всего, я как-то связан с тегом h1, ведь я так
близко к нему расположен!</p>
</body>
</html>

Поскольку компьютерные программы пишут в расчете на то, что их будут читать


люди, всегда полезно делать пометки в сложном коде. Вы будете встречать ком-
ментарии в HTML на протяжении этой книги и, скорее всего, столкнетесь с ком-
ментированным HTML в Интернете.

#
Мы познакомились с некоторыми примерами основных тегов и комментариев, что
же еще мы можем включить в разметку?
Сначала можем расширить тег <h1>, создав теги <h1>, <h2>, <h3>, <h4>, <h5> и <h6>.
Они представляют собой различные уровни заголовков и обычно сохраняются для
важных элементов содержимого страницы. Самый важный заголовок должен на-
ходиться в тегах <h1>, а менее значительные пусть появляются на нижних уров-
нях:
<!doctype html>
<html>
<head>
<title>Примеры тегов заголовков</title>
Привет, HTML!

</head>
<body>
<!-- Это самый главный заголовок -->
<h1>Самое важное!</h1>
<!-- Это сообщение, которое должно восприниматься как
очень важное
-->
<p>Важный абзац</p>
<h2>Менее важный заголовок</h2>
<p>А это какое-то менее важное сообщение</p>
</body>
</html>

Другой очень важный тег в HTML-документе — <a>, что означает anchor («якорь»)
и используется для создания ссылок. Теги ссылок — уникальная характеристика
гипертекста, так как они могут связывать информацию на текущей странице
и другой веб-странице. Чтобы использовать теги ссылок, нужно включить в HTML
атрибут href, который скажет браузеру, что именно нужно открыть в случае щел-
чка на ссылке. Атрибут href помещается внутрь открывающего тега:
<!doctype html>
<html>
<head>
<title>Примеры ссылок</title>
</head>
<body>
<!--
Атрибут href указывает, куда идти при щелчке на ссылке
-->
<p>Это <a href="http://www.google.com">ссылка</a> на Google!</p>
<p>
<a href="http://www.example.com">
Эта ссылка немного длиннее
</a>
</p>
<p>
А это ссылка на
<a href="http://www.facebook.com">www.facebook.com</a>
</p>
</body>
</html>

Когда мы откроем веб-страницу в браузере, то увидим нечто похожее на рис. 2.3.


Подчеркнутый текст означает, что на нем можно щелкнуть и перейти таким
образом на страницу, указанную в атрибуте href.
В этом примере есть только одна проблема — использование элементов абза-
ца для содержания, которое лучше выглядело бы в виде списка. Нет ли специ-
альных тегов для создания списка? Есть, и не один, а целых два! Теги <ol> и <ul>
служат для создания нумерованных (ordered) и маркированных (unordered) спис-
ков:
Глава 2. Структура

Рис. 2.3. Страница со ссылками с использованием тегов <a>

<!doctype html>
<html>
<head>
<title>Примеры списков</title>
</head>
<body>
<h1>Примеры списков!</h1>
<!-- Мы заключаем ссылки внутри тегов ul -->
<ul>
<li>
Это <a href="http://www.google.com">ссылка</a> на Google!
</li>
<li>
<a href="http://www.example.com">
Эта ссылка немного длиннее
</a>
</li>
<li>
А это ссылка на
<a href="http://www.facebook.com">
www.facebook.com
</a>
</li>
</ul>
<!-- Мы можем также создать нумерованный список -->
<h3>Как сделать нумерованный список</h3>
<ol>
<li>Начните с открывающего тега ol</li>
Привет, HTML!

<li>Затем добавьте несколько элементов в тегах li</li>


<li>Добавьте закрывающий тег ol</li>
</ol>
</body>
</html>

А затем обновите страницу в браузере — и вы увидите нечто похожее на


рис. 2.4.

Рис. 2.4. Страница с маркированным и нумерованным списками

Подытожим информацию, полученную из первых увиденных вами примеров. Во-


первых, все текстовое содержание помещается в HTML-теги.
Во-вторых, вы, наверное, заметили, что теги, заключенные в другие теги, смеще-
ны вправо с помощью отступов. Это делается потому, что HTML — иерархический
метод структурирования документов. Мы используем отступы как визуальную
пометку, напоминающую, что мы находимся на какой-либо ступени этой иерархии.
Это означает, что теги <head> и <body> заключены внутрь тега <html>, а теги <h1> и <p>,
в свою очередь, находятся внутри тега <body>. Случайным образом иногда мы поме-
щали ссылки на той же строке, что и их содержимое, а в других случаях — разбива-
ли строку. В HTML в большинстве случаев пробел не имеет значения.
И последнее по очереди, но не по важности: вы увидите, что в процессе создания
HTML-документа мы будем понемногу добавлять или изменять содержимое стра-
ницы, сохранять файл, а затем переходить к окну браузера и обновлять страницу.
Поскольку вы будете делать это очень часто, неплохо было бы попрактиковаться
Глава 2. Структура

несколько раз. Для начала добавьте несколько абзацев текста Lorem ipsum в тело
документа, а затем перейдите в браузер и перезагрузите страницу.

Вы будете проделывать эту операцию довольно часто, поэтому очень полезно выучить
клавиатурные сокращения для обновления страницы и переключения между активными
окнами в рабочей среде. В Windows и большинстве оболочек Linux вы можете исполь-
зовать Ctrl+Tab для переключения между активными окнами, а также Ctrl+R для обнов-
ления страницы. В Mac OS используйте Command+Tab и Command+R.

"’$ ’
$
Теги HTML образуют иерархическую структуру, называемую объектной моделью
документа (Document Object Model (DOM)). DOM — это способ представления
объектов, которые определяются через HTML, а затем взаимодействуют интерак-
тивно с помощью сценарного языка, например JavaScript. Теги HTML опреде-
ляют элементы DOM — сущности, находящиеся в модели.
Мы уже написали HTML способом, позволяющим визуализировать структуру
DOM. Вот почему мы отделяли отступами теги, находящиеся внутри других те-
гов, — это создает ощущение иерархии. Но хотя это и полезно для кода, не всегда
работает так ясно, как бы нам хотелось. Посмотрите вот на такой HTML:
<!doctype html>
<html>
<head>
<title>Привет!</title>
</head>
<body>
<h1>Привет!</h1>
<div>
<ol>
<li>Элемент списка</li>
<li> Элемент списка </li>
<li> Элемент списка </li>
</ol>
<p>Это абзац</p>
<p>Это <span>второй</span> абзац.</p>
</div>
<ul>
<li>Элемент списка<span>1</span></li>
<li>Элемент списка<span>2</span></li>
<li>Элемент списка<span>3</span></li>
</ul>
</body>
</html>
Использование валидации HTML для выявления проблем

В этом коде есть несколько тегов, с которыми вы еще не сталкивались, но пусть


это вас не беспокоит: понимать их назначение пока не обязательно. Важно, чтобы
вы обратили внимание: хотя этот код правильно разделен отступами, некоторые
теги все равно не выделены в отдельные строки. Например, элементы span нахо-
дятся на той же строке, что и элементы li. На самом деле это правильно, так как
тег <span> содержит всего один символ, но эта структура не иллюстрирует взаимо-
отношения между элементами так же хорошо, как отступы. Следовательно, нужен
другой способ визуализации этого примера.
В предыдущей главе мы обсуждали использование древовидной диаграммы для
создания ментальной модели файловой системы компьютера. Что ж, нам ничего
не мешает использовать ментальные модели для DOM! Эта ментальная модель
очень пригодится позднее, когда мы будем взаимодействовать с DOM с помощью
JavaScript. В качестве примера попробуем проиллюстрировать посредством дре-
вовидной диаграммы приведенный ранее код (рис. 2.5).
Эта диаграмма создает ясное представление о содержимом DOM. Кроме того,
она упрощает представление некоторых взаимоотношений: мы рассматриваем
элементы DOM, находящиеся в нижней части дерева, как потомки тех элементов,
что находятся выше, если существует путь, связывающий их. Непосредственные
потомки называются дочерними элементами, а элементы, находящиеся над дочер-
ними, являются по отношению к последним родительскими элементами.
В этом примере все элементы являются потомками элемента html, а элемент ul
является потомком элемента body. Элемент ul не является потомком элемента head,
так как не существует пути, начинающегося от элемента head и заканчивающегося
у элемента ul без продвижения вверх по иерархии. Элемент ul имеет три дочерних
элемента (это пункты списка — элементы li), а каждый элемент li имеет дочерний
span.
Мы изучим эти отношения подробнее по мере продвижения вперед, а пока
постарайтесь потренироваться в восприятии DOM именно таким образом.

$+70/
’#’’
Как я уже сказал в предыдущем разделе, текстовое содержание HTML-документа
обычно заключено в пару тегов. Открывающий тег выглядит как <html>, а закрыва-
ющий — как </html>. Точное имя тега означает тип элемента DOM, который он
собой представляет.
Если ваш документ становится слишком длинным, могут возникнуть некото-
рые проблемы. Например, представьте следующий HTML-документ, являющий-
ся определенным обобщением предыдущего примера, с несколькими новыми
тегами:
<!doctype html>
<html>
<head>
Рис. 2.5. Дерево, представляющее схему DOM
Глава 2. Структура
Использование валидации HTML для выявления проблем

<title>Мое первое веб-приложение</title>


</head>
<body>
<h1>Привет!</h1>
<nav>
<div>Войти</div>
<div>ВиО</div>
<div>О нас</div>
</nav>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmodtempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, <span>quis
nostrud exercitation</span> ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim <span>ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
aute irure dolor in reprehenderit in voluptate <span>velit esse cillum dolore eu
fugiat</span> nulla pariatur.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.</p>
</body>
</html>

Этот HTML-документ содержит ошибку, но если вы откроете его в браузере, то


не заметите ее. Попробуйте понять, где она, прежде чем читать дальше.
Нашли? Поздравляю — вы просто орлиный глаз! Если не нашли, ничего страш-
ного. Дело в том, что во втором предложении есть открывающий тег <span>, но нет
закрывающего. Большинство людей проводят много времени, пытаясь обнаружить
такие ошибки в начале работы. К счастью, существует автоматизированный способ
поиска ошибок в HTML-документах.
Программа валидации — это программа, которая автоматически проверяет, со-
ответствует ли код определенным базовым стандартам. Если вы изучали когда-то
язык программирования вроде Java или C++, вам, вероятно, приходилось работать
с компилятором. Если в коде есть ошибки, компилятор сообщит о них, пропустив
код через обработку. Языки вроде HTML немного «глупее» в том смысле, что брау­
зер позволит вам допустить некоторое количество ошибок, но валидационная
программа обнаружит то, что он пропустил.
Но почему мы вообще должны думать об ошибках, если браузер отображает
страницу абсолютно одинаково, есть закрывающий тег <span> или его нет? Дело
в том, что единственный путь гарантировать, что страница будет всегда выглядеть
корректно в любом браузере, — соблюдение правил HTML. Вот почему HTML-
валидатор является очень полезным инструментом.
Нам не нужно устанавливать никакого программного обеспечения для исполь-
зования валидатора. Начнем с визита на домашнюю страницу W3C’s Markup
Validation Service validator.w3.org. На момент написания книги она выглядит так,
как показано на рис. 2.6.
Глава 2. Структура

Рис. 2.6. Домашняя страница W3C’s Markup Validation Service

Вы, наверное, заметили вкладку Validate by Direct Input. Щелкнув на ней, мы мо-
жем вырезать и вставить наш HTML-код в появившееся текстовое поле. После
вставки кода нужно нажать большую кнопку Check.
Начнем с примера с Lorem ipsum, приведенного ранее. Если он не содержит
никаких ошибок, мы увидим что-то похожее на рис. 2.7.

Рис. 2.7. Валидатор W3C HTML после проверки примера с Lorem ipsum
Использование валидации HTML для выявления проблем

Валидируя HTML, вы, скорее всего, получите три предупреждения, даже если ошибок
нет. Первое из них сообщает, что валидатор использует правила для проверки HTML5.
Несмотря на то что стандарт HTML5 относительно стабилен, в нем все же могут быть
изменения, о чем и уведомляет это сообщение.
Другие два относятся к кодировке символов и в данный момент могут быть проигнори-
рованы. Если вам интересно, одно из предупреждений содержит ссылку на обучающий
материал по кодировке символов, который научит вас изменять кодировку HTML-доку-
мента.

Если код не проходит валидацию, то мы увидим список найденных ошибок.


Например, если запустим на проверку код с пропущенным закрывающим тегом
элемента span во втором абзаце, то увидим нечто похожее на рис. 2.8.

Рис. 2.8. Валидатор W3C HTML после проверки примера с ошибкой

Если мы прокрутим экран ниже, то увидим список найденных ошибок. Вали-


датор не способен точно указать, где проблемы, но если мы понимаем свой код, то
и сами их найдем. На рис. 2.9 показано, как валидатор описывает ошибки.
Всегда полезно периодически прогонять через программу-валидатор HTML-код,
чтобы убедиться в корректности последнего. Двигаясь к концу этой главы, я буду
периодически напоминать вам еще разок проверить HTML с помощью валидатора.
Пожалуйста, не забывайте делать это.
Глава 2. Структура

Рис. 2.9. Валидатор W3C HTML описывает допущенные ошибки

$PD]HULI¿F
В оставшейся части главы мы с вами создадим HTML для учебного веб-приложе-
ния. Таким образом мы убьем сразу двух зайцев: опробуем на настоящем проекте
рабочий процесс, изученный в предыдущей главе, а также рассмотрим еще несколь-
ко важных тегов HTML и отображаемых ими элементов.

#
Учебное приложение называется Amazeriffic, от слов amazing — «изумительный»
и terrific — «невероятный». Это название, конечно, немного глуповато, но не хуже
названия любой другой компании, размещающейся в наши дни в Кремниевой до-
лине. Предназначение продукта Amazeriffic состоит в отслеживании и категоризации
набора задач (по сути, это список необходимых дел). Далее в этой книге мы и в самом
деле поработаем над реализацией проекта, похожего на этот, но пока не освоимся
достаточно с HTML, сконцентрируемся на главной странице продукта. Страница,
которую мы создадим, будет похожа на рис. 2.10.
Помните, что HTML задает структуру документа. Это значит, что, даже если
мы видим здесь множество стилистических элементов (разные шрифты, цвета
и даже верстка элементов), по большей части их следует игнорировать, так как
к HTML они не имеют никакого отношения. Пока мы с вами концентрируемся
исключительно на структуре документа.
Amazeriffic

Рис. 2.10. Страница Amazeriffic, которую мы создадим в течение этой и последующих глав

Пока не перешли непосредственно к кодированию, даайте посмотрим, удастся


ли выделить различные части структуры. Попробуйте нарисовать эскиз этой стра-
ницы на листке бумаги карандашом, а затем пометить все структурные элементы
так тщательно, как только получится. Если вы не можете сообразить, что от вас
требуется, просто обведите все большие и маленькие элементы на странице, а затем
присвойте им описательные имена, которые показывают их роль в документе.
На рис. 2.11 вы можете посмотреть на версию предыдущего макета, где элемен-
ты обведены пунктирными линиями. Без труда можно заметить, что некоторые
элементы находятся внутри других. Таким образом мы видим взаимоотношения,
определяющие, какие элементы будут потомками других элементов в DOM, и это
поможет нам примерно прикинуть, каким будет HTML-код.
Нетрудно и подписать обведенные элементы. Например, вполне очевидно, где
находятся заголовок, логотип, навигационные ссылки, подвал, контактная инфор-
мация, карта сайта, основное содержание, второстепенное содержание и изображе-
ние. Все они представляют собой какие-то структурные элементы страницы.

Œ’#!$&
#
После идентификации всех структурных элементов нужно продумать, как они
будут сочетаться друг с другом. Чтобы сделать это, построим древовидную диа-
грамму для структуры, которая определит содержимое разных элементов. На рис. 2.12
показано древовидное представление возможной структуры.
Глава 2. Структура

Рис. 2.11. Макет Amazerrific, размеченный для представления структуры

ł’# 
Теперь, когда у нас есть древовидное представление страницы (как в голове, так
и на бумаге), очень легко написать код HTML, если мы знаем теги, необходимые
для всех элементов на странице. Поскольку вы еще не видели некоторые теги,
которые здесь упомянуты, я буду объяснять их назначение по ходу дела.
Прежде чем что-либо делать, создадим каталог для хранения проекта. Если вы
следовали инструкциям, приведенным в главе 1, у вас уже есть папка Projects в до-
машней папке (если вы в Mac OS или Linux) либо в каталоге Documents (если ра-
ботаете в Windows). Перейдем в эту папку из командной строки Tеrminal application
в Mac OS или из Git Bash в Windows. Используем команду cd:
hostname $ cd Projects

Находясь в этой папке, создадим каталог для хранения проекта Amazeriffic.


Какую команду нужно использовать? Правильно! Команду mkdir:
hostname $ pwd
/Users/semmy/Projects
hostname $ mkdir Amazeriffic

Затем мы можем получить некоторую визуальную обратную связь, говорящую


о том, что папка была успешно создана, и наконец перейти в нее с помощью коман-
ды cd:
Amazeriffic

Рис. 2.12. Структура Amazeriffic в виде древовидной диаграммы


Глава 2. Структура

hostname $ ls
Amazeriffic
hostname $ cd Amazeriffic

Таким образом, сейчас мы находимся в новой папке проекта (в чем можем убе-
диться с помощью команды pwd). Следующая очень важная задача — поместить эту
папку в систему контроля версий. Создадим проект Git с помощью команды git
init:
hostname $ git init
Initialized empty Git repository in /Users/semmy/Projects/Amazeriffic/.git/

Теперь, когда мы создали рабочую папку и поместили ее в систему контроля


версий, наконец-то можно приступить к кодированию! Запустите Sublime, а затем
откройте папку Amazeriffic, используя клавиатурное сокращение, описанное в пре-
дыдущей главе.
Затем мы можем создать новый HTML-документ, щелкнув правой кнопкой
мыши на навигационной панели и выбрав New File. В результате будет создан бе-
зымянный файл, который мы тут же переименуем, просто набрав index.html. После
того как файл создан и переименован, можем открыть его двойным щелчком. До-
бавим «Hello, World!», чтобы в документе было какое-то видимое в браузере со-
держимое.
Убедившись, что у нас есть рабочая основа, можно запустить Chrome и открыть
страницу. Если все идет хорошо, мы увидим в браузере «Hello, World!».
Теперь можно начать с построения HTML-скелета нашего документа. Замени-
те «Hello, World!» в файле index.html следующим содержимым:
<!doctype html>
<html>
<head>
<title>Amazeriffic</title>
</head>
<body>
<h1>Amazeriffic</h1>
</body>
</html>

Сохраните файл и еще раз откройте страницу в браузере. Сделав это, увидите
нечто похожее на рис. 2.13.
Проделав все это, можем выполнить наш первый коммит. Перейдите к команд­
ной строке. Сначала проверьте статус рабочей папки, а затем добавьте и зафикси-
руйте в системе контроля версий файл index.html:
hostname $ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
Amazeriffic

Рис. 2.13. Страница Amazeriffic после добавления нескольких базовых элементов

#
# index.html1
hostname $ git add index.html
hostname $ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: index.html2
hostname $ git commit -m "Add default index.html to repository."
[master (root-commit) fd60796] Add default index.html to the repository.
1 file changed, 10 insertions(+)
create mode 100644 index.html3
Вы видите, что начали мы с проверки статуса. Это очень хорошая привычка — ви-
зуальная обратная связь всегда полезна. Кроме всего прочего, если мы случайно
изменим файл, который не собирались менять, то получим подсказку об этом.
Сейчас, однако, мы видим здесь только один файл — index.html.

1
Системное сообщение: «От бранч-мастера. Начальный коммит. Неотслеживаемые
файлы (используйте "git add <file>..." для добавления их в список для фиксирования
изменений): новый файл index.html». — Примеч. пер.
2
Системное сообщение: «От бранч-мастера. Начальный коммит. Будут зафиксированы
изменения (используйте "git rm –cached <file>…" для отмены фиксации): новый файл
index.html». —  Примеч. пер.
3
Системное сообщение: «1 файл изменен, 10 вставок (+). Создан модуль 100644 in­
dex.html». — Примеч. пер.
Глава 2. Структура

Добавляем index.html и смотрим, что получилось, с помощью еще одного вызо-


ва git status. Это покажет нам, какие файлы войдут в коммит с командой git commit.
И наконец, выполним git commit, добавив соответствующее сообщение.
Теперь мы готовы по-настоящему приступить к построению структуры страни-
цы. Вернувшись к древовидной диаграмме, можем заметить, что у нас есть заголо-
вок, секция с основным контентом и подвал — нижняя часть страницы. Каждый из
них является потомком элемента body. В HTML есть специальные теги для каждой
из этих трех секций документа. Теги <header> и <footer> служат для элементов,
находящихся в самом верху и в самом низу страницы, а тег <main> представляет
секцию, где находится основное содержимое документа:
<!doctype html>
<html>
<head>
<title>Amazeriffic</title>
</head>
<body>
<header>
<h1>Amazeriffic</h1>
</header>
<main>
</main>
<footer>
</footer>
</body>
</html>
Обратите внимание: мы передвинули тег <h1>, содержащий название Amazeriffic,
внутрь тега заголовка. Это вызвано тем, что в нашей древовидной диаграмме на-
звание является потомком заголовка.
Затем обратимся к верхнему правому углу страницы, где находится небольшой
навигационный блок со ссылками на страницы Регистрация, ВиО, Техподдержка.
Очень удобно: в HTML есть специальный тег для создания навигационных эле-
ментов, который так и называется — nav. Добавим эту секцию в тег <header>:
<header>
<h1>Amazeriffic</h1>
<nav>
<a href="#">Регистрация</a> |
<a href="#">ВиО</a> |
<a href="#">Техподдержка</a>
</nav>
</header>

Обратите внимание: элемент nav содержит несколько ссылок, разделенных символом |.


Этот символ находится на вашей клавиатуре справа, над клавишей Enter, он совмещен
с обратным слэшем. Нужно удерживать клавишу Shift, чтобы набрать его.
Amazeriffic

Ссылки в элементе nav заключены в теги <a>. Как уже упоминалось, теги <a>
содержат атрибуты href, в которых обычно находится адрес страницы, куда мы
должны перейти, щелкнув по ссылке. Поскольку пока что наш пример не будет
содержать никаких настоящих ссылок, мы используем значок # как временную
заглушку для места, куда нужно потом вставить ссылку.
Завершив работу над секцией <header>, будет весьма неплохо зафиксировать ее
в хранилище Git. Сначала будет полезно выполнить команду git status, чтобы
увидеть измененные файлы в хранилище. Затем мы выполняем git add и git commit
с каким-то поясняющим сообщением об изменениях, содержащихся в коммите.

ø
После окончания работы над секцией <header> можем перейти к <main>. Из диаграм-
мы видно, что эта секция разделена на две основные части, как и заголовок. Там
есть содержимое, находящееся слева, и изображение справа. Текст слева разделен
на две отдельные секции, и нам придется принять это во внимание.
Чтобы структурировать текст на странице слева, понадобятся четыре новых
тега. Мы используем два заголовочных тега (<h2> и <h3>), которые представляют
собой заголовки менее важные, чем <h1>. Появится и тег <p>, который означает
содержимое абзаца. Кроме того, мы используем тег <ul>, чтобы создать маркиро-
ванный список, внутри которого теги <li> будут содержать элементы списка.
И последнее по порядку, но не по важности: нам понадобится тег <img>, чтобы
вставить изображение лампочки. Обратите внимание на то, что у тега <img> нет
соответствующего закрывающего элемента. Это потому, что HTML5 включает
набор элементов, относящихся к пустым (void). Пустые элементы обычно не под-
разумевают какого-либо содержимого внутри и, соответственно, не требуют закры-
вающего тега.
Зато, как вы сейчас увидите, у тега <img> есть необходимый атрибут alt. Он
содержит текстовое описание изображения. Это сделано для обеспечения доступ-
ности нашей страницы для слабовидящих пользователей, которые часто применя-
ют программы для чтения содержимого экрана при пользовании Интернетом.

Вы можете скачать изображение лампочки с сайта http://www.learningwebappdev.com/


lightbulb.png. Чтобы оно появилось на вашей странице, необходимо сохранить его в ту
же самую папку, где находится файл index.html.

После добавления структурированного содержания в тег <main> мы получим


примерно такой код:
<h2>Amazeriffic изменит вашу жизнь!</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<h3>Почему вам нужен Amazeriffic?</h3>
<ul>
Глава 2. Структура

<li>Он легко впишется в вашу жизнь</li>


<li>Он классный</li>
<li>Он перевернет ваш мир</li>
</ul>
<img src="lightbulb.png" alt="Изображение лампочки">

На этом этапе неплохо будет прогнать код через валидатор и убедиться, что вы
ничего случайно не пропустили. Если все хорошо, сделайте еще один коммит в наше
хранилище Git, а затем перейдем к подвалу.

ø
Подвал, как и два предыдущих блока, содержит две основные логические части. Одна
из них включает информацию о компании, а другая — набор ссылок, которые можно
назвать картой сайта. Более того, сама карта сайта поделена на две колонки.
В первую очередь следует отметить, что элемента HTML специально для кон-
тактной информации не существует. Это нормально, так как HTML предоставля-
ет два тега общего назначения, <div> и <span>, которые позволяют создавать разно-
образные элементы, структуру которых мы определяем самостоятельно. Разницу
между div и span рассмотрим в следующей главе.
В данном случае в подвале есть две разные структуры: контактная информация
и карта сайта. Таким образом, нам понадобятся два элемента <div>, у каждого из
которых есть свой атрибут class, с помощью которого указывается тип элемента.
Пока запомните, что атрибут class — это свойство, с помощью которого вы указы-
ваете назначение элементов <div> и <span>.
Кроме того, мы используем еще один тег <ul> для создания маркированного
списка элементов карты сайта. В результате у нас получится вот такой HTML для
структуры подвала:
<footer>
<div class="contact">
<h5>Свяжитесь с нами</h5>
<p>Amazeriffic!</p>
<p>555 50-я улица</p>
<p>Эшвилл, Северная Каролина 28801</p>
</div>
<div class="sitemap">
<h5>Карта сайта</h5>
<ul>
<li><a href="#">Домой</a></li>
<li><a href="#">О нас</a></li>
<li><a href="#">Право</a></li>
<li><a href="#">Техподдержка</a></li>
<li><a href="#">ВиО</a></li>
<li><a href="#">Вакансии</a></li>
</ul>
</div>
</footer>
Больше теории и практики

Добавьте содержание подвала в HTML-документ, проверьте его в HTML-вали-


даторе, чтобы убедиться в отсутствии ошибок, а затем отправьте в хранилище Git
очередной коммит.
Мы вернемся к этому примеру в главе 3, когда займемся его стилевым оформ-
лением.

В этой главе мы научились создавать структуру пользовательского интерфейса


приложения с помощью HTML. HTML — это язык разметки, который позволяет
нам использовать теги для определения структуры, называемой объектной моделью
документа (Document Object Model (DOM)). Браузер использует DOM для созда-
ния визуального представления страницы.
DOM является иерархической структурой и легко может быть представлена
в виде древовидной диаграммы. Иногда полезно представлять себе DOM в виде
дерева, так как это более ясно показывает отношения между элементами: предками,
потомками и родительскими.
Валидатор — полезный инструмент, помогающий нам избежать простых ошибок
и просчетов при построении HTML.
В этой главе мы также изучили несколько тегов, которые перечислены в табл. 2.1.
Они представляют собой специфические элементы структуры, за исключением
тега <div>. К тегу <div> обычно добавляется атрибут class, с помощью которого
указывается значение этого элемента.
Таблица 2.1. Теги HTML
œ
<html> Основной контейнер HTML-документа
<head> Содержит метаинформацию о документе
<body> Содержит то, что будет отображаться в браузере
<header> Заголовочная часть страницы
<h1> Самый важный заголовок (в документе обычно только один)
<h2> Второй по важности заголовок
<h3> Третий по важности заголовок
<main> Область основного содержания документа
<footer> Подвал (нижняя часть) документа
<a> Ссылка на другой документ или страницу, куда переходят по щелчку
<ul> Список элементов, для которых порядок перечисления неважен (маркированный)
<ol> Список элементов, для которых порядок перечисления важен (нумерованный)
<li> Элемент списка (любого)
<div> Контейнер для подструктуры

Ø$  
В коде Amazeriffic я оставил несколько ошибок. Рекомендую вам исправить их
и заставить код работать настолько хорошо, насколько вы сможете. Помните: если
Глава 2. Структура

возникнут какие-то проблемы, вы всегда можете обратиться к финальному вари-


анту HTML на нашей странице GitHub.

Выучив основы HTML, мы можем добавить несколько новых элементов к задачам


на заучивание. В дополнение к пяти шагам, упомянутым в предыдущей главе, вы
можете выполнять следующие дополнительные упражнения.
1. Откройте файл в Chrome, используя клавиатурные сокращения.
2. Измените файл index.html , включив в него теги <!doctype> , <html> , <head>
и <body>.
3. Добавьте тег <p>, содержащий просто слова «Hello, World!».
4. Обновите файл в Chrome и убедитесь, что он отображается корректно (если это
не так, исправьте ошибки).
5. Отправьте новый коммит в хранилище в Git из командной строки.
6. Добавьте в тег <body> теги <header>, <main> и <footer>.
7. Убедитесь, что все корректно отображается в Chrome.
8. Проверьте файл с помощью HTML-валидатора.
9. Отправьте изменения файла index.html в хранилище Git.

##
Нарисуйте древовидную диаграмму для следующего HTML-документа. Мы еще
раз используем этот документ в качестве практической задачи в конце глав 4 и 5:
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Привет!</h1>
<h2 class="important">Hi again</h2>
<p class="a">Абзац любого текста</p>
<div class="relevant">
<p class="a">Первый</p>
<p class="a">Второй</p>
<p>Третий</p>
<p>Четвертый</p>
<p class="a">Пятый</p>
<p class="a">Шестой</p>
<p>Седьмой</p>
</div>
</body>
</html>
Больше теории и практики

ø#Œ)$4’$PD]HULI¿F
В навигационной панели Amazeriffic находится ссылка на страницу ВиО (Вопросы
и ответы), закрытая заглушкой. Создайте страницу с точно такими же заголовком
и подвалом, как на главной, но содержащую список вопросов и ответов в основной
части. Используйте текст Lorem ipsum (если, конечно, не хотите придумать текст
вопросов и ответов сами).
Сохраните этот файл как faq.html. Можете связать наши две страницы с помо-
щью атрибута href в теге <a>, установив его значение faq.html. Если вы поместите
оба файла в одну и ту же папку, то можете щелкнуть на ссылке, находящейся на
главной странице, и оказаться на странице ВиО. Можете установить и ссылку об-
ратно на страницу index.html с faq.html.

Ø$ +70/
На протяжении всей книги я буду рекомендовать вам Mozilla Developer Network
documentation для дальнейшего изучения определенных тем. Этот сайт содержит
замечательное описание HTML. Я рекомендую углубленно изучить документацию
и расширенные возможности.
ø$

В предыдущей главе мы изучили создание структуры HTML-документа и некото-


рые соответствующие ментальные модели. Но созданные нами страницы оставля-
ют желать лучшего с точки зрения стиля и дизайна.
В этой главе мы постараемся решить некоторые из этих проблем, изучив, как
можно изменить отображение HTML-документов с помощью каскадных таблиц
стилей (Cascading Style Sheets (CSS)). Как упоминалось в предыдущей главе, это
даст вам достаточно информации для начала работы с CSS, а из раздела «Больше
теории и практики» данной главы вы узнаете, как эксплуатировать эти ресурсы.

&66
Чтобы сделать первые шаги, начнем с простого HTML-примера, похожего на на-
чальные упражнения в предыдущей главе. Откройте командную строку и создайте
каталог под названием Сhapter3 (Глава 3) в папке Projects.
А сейчас запустите Sublime Text и откройте папку Chapter3 так, как мы это де-
лали в предыдущей главе. Создайте следующий HTML-файл и сохраните его в этой
папке как index.html:
<!doctype html>
<html>
<head>
<title>Мое первое веб-приложение</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h1>Всем привет!</h1>
<p>Это абзац.</p>
</body>
</html>

В этом файле составлена очень простая страница HTML с элементами h1 и p,


содержащимися в элементе body. Вы, конечно, обратили внимание на новый тег,
находящийся внутри тега <head>. Тег <link> связывает наш файл с другим, в котором
показан стиль отображения документа.
Привет, CSS!

Вы можете создать файл style.css с помощью Sublime из навигационного фай-


лового окна в редакторе. Содержание файла пусть будет следующим:
body {
background: lightcyan;
width: 800px;
margin: auto;
}
h1 {
color: maroon;
text-align: center;
}
p {
color: gray;
border: 1px solid gray;
padding: 10px;
}

Это простой пример файла CSS. В этом конкретном файле мы устанавливаем


для элемента body светло-голубой фон (lightcyan) и указываем браузеру, что текст,
содержащийся в элементе h1, должен быть светло-коричневым (maroon). Кроме того,
мы задаем ширину области body 800 пикселов и устанавливаем ширину полей auto,
благодаря чему область body будет выровнена по центру страницы. И наконец, мы
задаем для текста, находящегося в элементе p, серый цвет и создаем вокруг него
тонкую границу.
По сути дела, файл CSS описывает, как определенные элементы HTML будут
отображаться в браузере. Например, на рис. 3.1 показано, как предыдущий HTML
взаимодействует с файлом CSS.

Рис. 3.1. Файл index.html, связанный с таблицей стилей и открытый в Chrome

Если бы мы не включили CSS, файл выглядел бы так, как продемонстрировано


на рис. 3.2.
Глава 3. Стиль

Рис. 3.2. Файл index.html, не связанный с таблицей стилей и открытый в Chrome

Если файлы index.html и style.css находятся в одной и той же папке, то вы


можете открыть первый из них в браузере и увидеть, что он похож на рис. 3.1.
Фактически если вы создадите еще одну страницу HTML с другим содержанием
и свяжете ее с тем же самым файлом CSS, то обе страницы будут отображаться
так, как указано в этой таблице стилей. Это одно из главных преимуществ CSS —
он позволяет стилизовать сразу несколько страниц с помощью всего одного
файла!

ı#
Файл CSS — это коллекция наборов правил, а набор правил — это просто ряд ука-
заний относительно стилевого оформления, которые применяются к какому-либо
виду элементов в DOM (как вы помните, это иерархическая система объектов
в HTML-документе). Набор правил состоит из селектора — это может быть, на-
пример, название тега, открывающей фигурной скобки, списка правил и закрыва-
ющей фигурной скобки. Каждое правило состоит из определенного свойства,
после которого следуют двоеточие, значение этого свойства (или список значений,
разделенных пробелами), после чего ставится точка с запятой, например:
body {
width: 800px;
background: lightcyan;
color: #ff0000;
}

Это пример набора правил, применяемых в DOM к элементу body. В данном


случае селектор — это body, то есть просто название элемента HTML, к которому
мы хотим применить стиль. Эти правила будут применяться ко всему содержимо-
му элемента body, то есть ко всем элементам, находящимся внутри него. В набор
Привет, CSS!

входят три правила: первое определяет значение свойства width (ширина), вто-
рое — свойства background (фон), а третье — свойства color (цвет).

В CSS существует два способа указания цвета. Первый — названия наиболее часто
употребляемых в CSS цветов. Второй — указание шестнадцатеричного цветового кода.
Этот код состоит из шести цифр шестнадцатеричной системы счисления (0–9 или A–F).
Первая пара определяет количество в цвете красного, вторая — зеленого, а третья — си-
него. Эти три цвета являются основными в цветовой модели RGB.

Опытный разработчик CSS хорошо знает типы свойств, которые могут быть
установлены для какого-либо элемента, а также разбирается во всем разнообразии
их значений. Большинство свойств, например цвет фона или шрифта, могут при-
меняться к очень многим элементам HTML.

В наш файл CSS можно также добавлять комментарии. Как и в HTML, коммента-
рии в CSS являются просто пометками к коду, которые браузер полностью игно-
рирует. Например, мы можем добавить комментарии к предыдущему набору правил
следующим образом:
/* Здесь находится стиль для элемента body */
body {
width: 800px; /* Устанавливаем ширину body 800 пикселов */
background: lightcyan; /* Устанавливаем светло-голубой цвет фона */
color: #ff0000; /* Устанавливаем красный цвет для элементов*/
}

Некоторые советуют свободно писать в программах комментарии. Но я все-таки


склоняюсь к мысли, что лучше позволить коду говорить самому за себя, насколько
это возможно, и минимизировать необходимость комментариев. На самом деле
опытные разработчики CSS, скорее всего, сочтут приведенные ранее комментарии
избыточными, да и вы, когда узнаете обо всех этих элементах несколько больше,
перестанете в них нуждаться. В то же время бывают ситуации, когда неочевидно,
что должен делать код, тогда сделать некоторые пометки весьма полезно.
Если вы только начинаете работать с CSS, я бы рекомендовал вам писать ком-
ментарии чаще и подробнее. В оставшейся части главы я использую комментарии
в свободной форме, чтобы сделать материал более понятным.

##’
В большинстве своем элементы HTML отображаются двумя способами. Пер-
вый — внутристрочный, который применяется, например, к элементам span. Это
значит, кроме всего прочего, что содержимое тега будет появляться на той же стро-
ке, что и окружающие его элементы:
Глава 3. Стиль

<div>
Этот абзац и это <span>слово</span> отображаются внутри строки. Эта
<a href="http://www.example.com">ссылка</a> тоже отображается внутри строки.
</div>

Если мы добавим этот элемент в тег body в нашем index.html, то страница будет
отображаться, как показано на рис. 3.3.

Рис. 3.3. Пример внутристрочного отображения элемента внутри блочного элемента

Но чаще элементы являются блочными, а не внутристрочными. Это значит, что


содержимое, находящееся внутри элемента, будет отображаться с новой строки по
отношению ко всему тексту. К блочным элементам, с которыми мы уже знакомы,
относятся p, nav, main и div:
<div>
Этот абзац и это <div>слово</div> отображаются внутри строки. Эта
<a href="http://www.example.com">ссылка</a> тоже отображается внутри строки.
</div>

Этот код будет выглядеть несколько иначе (рис. 3.4).

Рис. 3.4. Пример блочного элемента, находящегося внутри другого блочного элемента
Привет, CSS!

Стили как блочных, так и внутристрочных элементов имеют свойства цветов


фона и элемента. Но в стиле блочных элементов содержатся еще три важных свой­
ства, которые очень полезны при настройке компоновки страницы: отступы
(padding), границы (border) и поля (margin).
Отступ представляет собой пространство между содержанием элемента и его
границей, а поля — пространство между самим элементом и его контейнером. Гра-
ницей же является пространство между отступом и полями. Эта схема показана на
рис. 3.5.

Рис. 3.5. Поля, граница и отступы блочного элемента DOM

Рассмотрим простой пример, который поможет изучить манипулирование


размерами полей, отступов и границы для различных элементов. Создадим файл
margin_border_padding.html:
<!doctype html>
<html>
<head>
<title>Глава 3 — Пример границ, отступов и полей</title>
<link href="margin_border_padding.css" rel="stylesheet" type="text/css">
</head>
<body>
<div>
<p>ЭТО АБЗАЦ, НАХОДЯЩИЙСЯ ВНУТРИ ЭЛЕМЕНТА DIV</p>
</div>
</body>
</html>

Еще нам понадобится файл margin_border_padding.css, на который мы ссылались


в файле HTML ранее. Вы можете создать эти файлы в одной папке в Sublime, а за-
тем открыть файл margin_border_padding.html в Chrome:
Глава 3. Стиль

body {
background: linen;
width: 500px;
margin: 200px auto;
}
div {
border: 5px solid maroon;
text-align: center;
padding: 5px;
}
p {
border: 2px dashed blue;
}

Если вы все набрали правильно, то страница будет выглядеть так, как показано
на рис. 3.6.

Рис. 3.6. Пример для изучения полей, отступов и границы

Вы можете видеть, что все элементы (и div, и p) являются блочными и имеют


собственные границу, поля и отступы. Если вы этого еще не добились, попробуйте
повторить HTML и CSS, показанные здесь. Затем поупражняйтесь немного в из-
менении свойств padding, border и margin каждого элемента, чтобы разобраться, как
они влияют на отображение страницы.

ø#
Самый важный аспект в CSS — эффективное использование селекторов. Мы уже
видели базовый тип селекторов, в качестве которых используется название тега —
для применения стиля выбираются все теги с таким именем. Например, рассмотрим
следующий HTML:
Селекторы

<body>
<h1>Привет!</h1>
<p>Это абзац.</p>
<p>Это второй абзац.</p>
</body>

Предположим, что для применения к нему стиля используется следующий


CSS:
h1 {
background: black;
color: white;
}
p {
color: red;
margin: 10px;
padding: 20px;
}

Первый набор правил оформляет элемент h1, а второй — оба элемента p. Во мно-
гих случаях нам будет требоваться именно это. Однако в других мы можем захотеть
применить к первому и второму абзацам разные стили.

#
В главе 2 мы уже видели, что можно добавить атрибут class к тегам <div>, чтобы
отличать один от другого. На самом деле мы можем добавить класс к любому эле-
менту DOM. Например, можем переписать предыдущий HTML следующим обра-
зом:
<body>
<h1>Привет!</h1>
<p class="first">Это абзац.</p>
<p class="second">Это второй абзац.</p>
</body>

Теперь мы можем применить стиль к определенному абзацу в соответствии с его


классом:
h1 {
background: black;
color: white;
}
p.first {
color: red;
margin: 10px;
padding: 20px;
}

В этом примере набор правил p.ruleset будет применяться только к первому


элементу p, то есть к первому абзацу. Если класс появляется только у определенного
Глава 3. Стиль

типа элементов (как обычно и происходит), то мы можем опустить имя тега и прос-
то использовать класс:
.first {
color: red;
margin: 10px;
padding: 20px;
}

В главе 2 мы убедились, что можем создавать кликабельные элементы DOM с по-


мощью тега <a>:
<body>
<a href="http://www.example.com">Click Me!</a>
</body>

Элементу а может быть присвоен стиль точно так же, как любому другому эле-
менту DOM, — с помощью CSS. Например, мы можем создать файл CSS, который
изменяет цвета всех ссылок:
a {
color: cornflowerblue;
}

Часто бывает полезно менять цвет ссылки в зависимости от того, нажимал на


нее пользователь или еще нет. CSS позволяет изменить цвет однажды нажатых
ссылок добавлением нового набора правил:
a {
color: cornflowerblue;
}
a:visited {
color: tomato;
}

В этом коде visited — пример псевдокласса CSS для элемента a. Он ведет себя
почти так же, как и обычный класс, и мы можем присвоить элементам стили с его
помощью, как будто он является обычным классом. Главное отличие состоит в том,
что браузер самостоятельно добавляет этот класс.
Основной случай использования псевдоклассов CSS — изменение способа
отображения ссылки после того, как пользователь наводит на нее указатель мыши.
Этого можно достичь с помощью псевдокласса hover для элемента a. В следующем
примере мы изменяем предыдущий пример так, чтобы ссылка становилась подчерк­
нутой, только когда пользователь наводит на нее указатель мыши:
a {
color: cornflowerblue;
text-decoration: none; /* убираем подчеркивание по умолчанию */
}
Селекторы

a:visited {
color: tomato;
}
a:hover {
text-decoration: underline;
}

Ø##
По мере разрастания древовидной диаграммы DOM появляется необходимость
в более сложных селекторах. Рассмотрим, например, такой HTML:
<body>
<h1>Привет!</h1>
<div class="content">
<ol>
<li>Элемент списка <span class="number">первый</span></li>
<li>Элемент списка <span class="number">второй</span></li>
<li>Элемент списка <span class="number">третий</span></li>
</ol>
<p>Это <span>первый</span> абзац.</p>
<p>Это <span> первый</span> абзац.</p>
</div>
<ul>
<li>Элемент списка <span class="number">1</span></li>
<li>Элемент списка <span class="number">2</span></li>
<li>Элемент списка <span class="number">3</span></li>
</ul>
</body>

В этом HTML-коде два списка, несколько абзацев, несколько элементов списка.


Мы можем выбрать основные элементы и присвоить им стили, как обсуждалось
ранее. Например, если нужна закругленная граница вокруг нумерованного списка
(ol), можем использовать следующий набор правил:
ol {
border: 5px solid darksalmon;
border-radius: 10px;
}

А теперь допустим, что мы хотим сделать элементы нумерованного списка ко-


ричневыми. Для этого можно было бы прибегнуть к следующему:
li {
color: brown;
}

Но таким образом мы изменим все элементы во всех списках — и в маркирован-


ном, и в нумерованном. Можно поступить более конкретно и указать селектор
только для элементов li в нумерованном списке:
Глава 3. Стиль

ol li {
color: brown;
}
Если на странице несколько нумерованных списков, может понадобиться еще
больше конкретики. Тогда мы укажем, что стиль относится только к тем элементам
li, которые являются потомками элемента div класса content:
.content li {
color: brown;
}
А теперь допустим, что надо сделать фон первого элемента всех списков жел-
того цвета. Для этого существует псевдокласс first-child, указывающий на первый
дочерний элемент каждого родителя:
li:first-child {
background: yellow;
}
Аналогично для второго, третьего и четвертого потомков мы можем использо-
вать псевдокласс ntn-child:
li:nth-child(2) {
background: orange;
}

#
Что если два разных набора правил используют селекторы, указывающие на один
и тот же элемент в HTML? Например, представим, что у нас есть элемент p с клас-
сом greeting:
<p class="greeting">Приветствуем каскадные правила!</p>
А затем напишем два правила, которые применяют к этому элементу разные
стили:
p {
color: yellow;
}
p.selected {
color: green;
}
Какое же из правил будет применено к указанному ранее элементу? Оказыва-
ется, мы получили набор каскадных правил, который браузер будет применять в слу-
чае конфликта. В данном случае более конкретное правило (класс) получает пре-
имущество. Но что произойдет, если мы напишем что-то подобное:
p {
color: yellow;
}
Селекторы

p {
color: green;
}

В этом случае будет применяться набор правил, указанный в списке CSS позд-
нее. Следовательно, абзац или абзацы окажутся зеленого цвета. Если мы поменяем
правила местами, абзац станет желтым.

ı
Если вы еще не заметили, потомки наследуют свойства своих родителей. Что это
значит в отношении применения стилей к элементам? Если мы устанавливаем для
элемента какой-либо стиль, все его потомки в DOM будут оформлены точно так
же, если только для них специально не указан какой-либо другой стиль. Так, на-
пример, если мы изменяем свойство color для элемента body, все элементы, явля-
ющиеся потомками body (то есть все элементы, отображающиеся на странице),
будут наследовать этот цвет. Это самая суть CSS — вот почему очень полезно
визуализировать DOM и держать в голове иерархию во время присвоения стилей
элементам:
body {
background: yellow;
}
/**
* Поскольку h1 является потомком тега body,
* у него будет желтый фон
*/
h1 {
color: red;
}
/**
* h2 тоже потомок элемента body, но мы
* перекрываем правило для его фона,
* он не будет желтым
*/
h2 {
background: green;
}

Большая часть CSS-свойств работает именно таким способом. Надо отметить,


что не все свойства наследуются по умолчанию. Основная часть ненаследуемых
свойств относится к блочным элементам (правила для полей, отступов, границ
элементы не наследуют от своих родителей):
body {
margin: 0;
padding: 0;
}
/**
Глава 3. Стиль

* h1 не унаследует правила для полей и отступов от body,


* даже если мы не укажем иного
*/
h1 {
}

&!’
Мы познакомились с элементами, влияющими на базовый стиль элементов DOM.
Но существуют и другие, более общие свойства, которые влияют на всю компонов-
ку страницы относительно единичного элемента. Эти свойства значительно рас-
ширяют возможности разработчика в части управления расположением элементов
на странице. Одно из самых часто используемых свойств — float. Это свойство
может помочь нам создать более гибкие компоновки, чем статичная разметка, ко-
торую HTML создает автоматически.
Свойство элементов DOM под названием float может иметь значения left
и right. Оно устанавливает элемент вне рабочего потока (согласно которому, как
правило, элементы располагаются сверху вниз один за другим) и выравнивает его
по левой или правой стороне элемента-контейнера при условии, что есть достаточ-
но места, чтобы это сделать. Например, рассмотрим такой фрагмент HTML:
<body>
<main>
<nav>
<p><a href="link1">Ссылка 1</a></p>
<p><a href="link2">Ссылка 2</a></p>
<p><a href="link3">Ссылка 3</a></p>
<p><a href="link4">Ссылка 4</a></p>
</nav>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur.
</p>
</main>
</body>

В этом примере элементы nav и p находятся внутри элемента main. Мы также со-
здали отдельный элемент p для каждой ссылки, потому что хотим, чтобы элементы
отображались как блочные (каждая на отдельной строке). Без присвоения им стилей
эти элементы будут располагаться друг за другом вертикально (рис. 3.7).
А сейчас попробуем применить к этому HTML следующий CSS:
main {
width: 500px;
margin: auto;
Селекторы

Рис. 3.7. Отображение страницы перед применением CSS

background: gray;
}
nav {
/* Уберите пометку комментария со следующей строки, если хотите иметь черную гра-
ницу */
/* border: 3px solid black; */
width: 200px;
float: right;
}
p {
margin: 0; /* Ноль от границы по умолчанию */
}
Результат будет похож на рис. 3.8.

Рис. 3.8. Пример размещения элементов справа


Глава 3. Стиль

Отметим, что мы задали ширину навигационного элемента равной 200 пикселам


и разместили его справа, чтобы создать боковую панель. Обратите внимание, каким
образом элемент nav был извлечен из стандартной вертикальной разметки и раз-
мещен справа. Содержание элемента p обтекает элемент div, содержащий в себе nav.
Я также включил сюда линию — вы можете раскомментировать ее, чтобы вокруг
плавающего элемента появилась визуальная граница. Попробуйте сделать это!
Хотя установка элемента плавающим справа отлично работает для изображений
и других внутренних элементов, которые может обтекать текст, часто нужно создать
компоновку, подобную сетке и состоящую из двух колонок. Эта задачка немного
интереснее. В этом случае нужно сдвинуть элемент p влево и убедиться, что сум-
марный размер двух элементов не превышает размер контейнера. Вот как можно
этого достичь с помощью CSS:
main {
width: 500px;
margin: auto;
background: gray;
}
nav {
width: 100px;
float: right;
}
/* убираем значения по умолчанию для p, наследуемые от элемента nav */
nav p {
margin: 0;
padding: 0;
}
p {
margin: 0; /* убираем значения полей для р по умолчанию */
float: left;
width: 400px;
}
Сейчас, если мы откроем страницу в браузере, то увидим, что у нас отлично
вышли две колонки, но исчез серый фон. Это потому, что, когда все элементы, со-
держащиеся в контейнере, становятся плавающими, высота контейнера равняется
нулю. Исправить это очень просто — устанавливаем для контейнера div свойство
overflow со значением auto:
main {
width: 500px;
margin: auto;
background: gray;
overflow: auto;
}
Таким образом мы получили разметку, аналогичную показанной на рис. 3.9.
Отметим, что в этом примере мы установили суммарную ширину левого и пра-
вого элементов, равную 500 пикселам, что точно соответствует ширине контейне-
ра div. Поэтому, если мы добавим какие-то отступы, поля, границы или другие
Селекторы

элементы, у нас возникнут проблемы. Например, мы можем захотеть слегка ото­


двинуть текст в элементе p от края. Это потребует применения отступов, но если мы
добавим к элементу отступ 20 пикселов, то получим что-то похожее на рис. 3.10.

Рис. 3.9. Простая разметка в две колонки с использованием плавающих элементов

Рис. 3.10. Добавление отступов разбивает нашу разметку

Так произошло потому, что сумма пикселов в элементе main больше его ширины.
Мы можем исправить это, уменьшив ширину с учетом двойной величины отступа
(так как правый и левый отступы равны 10 пикселам). В результате CSS будет
выглядеть так:
Глава 3. Стиль

main {
width: 500px;
margin: auto;
background: gray;
overflow: auto;
}
nav {
width: 100px;
float: right;
}
p {
margin: 0; /* убираем поля по умолчанию для элемента p */
padding: 10px;
float: left;
width: 380px; /* 400 — 2*10 = 380 */
}

Тот же прием можно использовать при добавлении к элементам границы или


полей ненулевой ширины.

øFOHDU
При создании разметки с помощью плавающих элементов можно столкнуться
с интересной проблемой. Рассмотрим немного другой документ HTML. Сейчас
наша цель — установить навигацию с левой стороны, а подвал оставить охватыва-
ющим обе колонки.
<body>
<nav>
<p><a href="link1">Ссылка 1</a></p>
<p><a href="link2">Ссылка 2</a></p>
<p><a href="link3">Ссылка 3</a></p>
<p><a href="link4">Ссылка 4</a></p>
</nav>
<main>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur.
</p>
</main>
<footer>
<p>Это подвал</p>
</footer>
</body>

Следующий CSS-код отправляет элемент nav в правую часть страницы и обну-


ляет поля и отступы по умолчанию с помощью универсального селектора, который
Селекторы

выбирает все элементы DOM. Он также устанавливает для фона элемента разные
оттенки серого цвета (рис. 3.11):
* {
margin: 0;
padding: 0;
}
nav {
float: left;
background:darkgray;
}
main {
background:lightgray;
}
footer {
background:gray;
}

Рис. 3.11. Обратите внимание: подвал находится под секцией main, вместо того чтобы
располагаться снизу всего содержимого страницы

По сути, подвал находится с правой стороны разметки, под главной секцией.


А мы бы хотели, чтобы подвал был под обоими элементами. Вот для чего нужно
свойство clear. Мы можем указать элементу footer расположение под плавающим
элементом слева, справа или под обоими, левым и правым, элементами на страни-
це, указав соответствующее значение свойства clear. Вот как можно изменить CSS
для подвала:
footer {
background:gray;
clear: both; /* в данном случае можем использовать и 'clear: left'*/
}

Отображение получится похожим на рис. 3.12 — теперь подвал находится под


обоими плавающими элементами, как мы и хотели.
Для начинающих плавающие элементы обычно являются самым трудным ас-
пектом в CSS, поэтому я рекомендую вам потратить немного времени на экспери-
менты с созданием разметки.
Глава 3. Стиль

Рис. 3.12. Мы зафиксировали компоновку, установив свойство clear для подвала

ł
В прошлом работа с нестандартными шрифтами на сайтах была очень проблема-
тичной, потому что вы были ограничены теми из них, которые имелись на компью-
терах ваших пользователей. Веб-шрифты значительно упростили эту задачу, так
как вы можете запрашивать их прямо из Интернета, когда в них возникает необхо-
димость. В Google сейчас хранится множество нестандартных шрифтов, использо-
вать которые очень просто.
Вы можете начать с домашней страницы Google Fonts (рис. 3.13). Здесь мы
видим длинный список шрифтов и сразу можем просмотреть их отображение.
В этом примере мы используем шрифт, который называется Denk One. Когда
я зашел на домашнюю страницу Google Fonts, это был самый первый шрифт в спис-
ке. Чтобы использовать этот шрифт, нажмите кнопку Quick-use (Быстрое исполь-
зование) — это одна из кнопок, расположенных слева от большой синей кнопки
Add to Collection.
Нажав ее, мы увидим инструкции по использованию шрифта на нашей страни-
це. Нужно добавить тег <link> в секцию head HTML. Если мы используем более
ранний пример, то можем просто скопировать и вставить тег <link> со страницы
быстрого использования или самостоятельно написать следующее:
<head>
<title>Amazeriffic</title>
<!-- Вставим пустую строку для удобства логического разделения -->
<link href="http://fonts.googleapis.com/css?family=Denk+One"
rel="stylesheet" type="text/css">
<link href="style.css" rel="stylesheet" type="text/css">
</head>
Работа со шрифтами

Рис. 3.13. Домашняя страница Google Fonts1

HTML допускает применение как одинарных ('), так и двойных (") кавычек для ограни-
чения строк и значений. В примерах в этой книге я старался придерживаться использо-
вания двойных кавычек, но в скопированном и вставленном коде Google видим одинар-
ные. Поскольку они эквивалентны друг другу, можете исправить их на двойные или
оставить одинарными, значения это не имеет.

Таким образом этот веб-шрифт стал доступным на нашей странице. Теперь мы


можем использовать его в файле CSS. Например, чтобы установить шрифт Denk
One для элемента h1, надо добавить в набор правил свойство font-family:
h1 {
font-family: 'Denk One', sans-serif;
}

Первая часть записи указывает желаемый шрифт (который стал доступным


для применения с помощью тега <link>), а вторая — тот шрифт, который будет
использоваться, если заданный нами окажется недоступным. То есть, по сути, эта
запись говорит: «Применяй Denk One, если он доступен, а если нет, используй
шрифт по умолчанию из семейства sans-serif, установленный в системе пользо-
вателя».

1
На рисунке показаны шрифты, выбранные с применением фильтра Script:Cyrillic
(находится на панели слева, значение по умолчанию — Latin). Большинство кириллических
шрифтов, разумеется, поддерживают латинский алфавит, но, к сожалению, не наоборот. —
Примеч. пер.
Глава 3. Стиль

Но если отображать шрифты и изменять их цвет (с помощью свойства color


соответствующего элемента) очень просто, то как быть с их размером? В прошлом
существовало несколько способов это сделать, но в итоге остался один способ,
признанный всеми лучшим.
В первую очередь нужно учесть, что пользователь может изменять шрифт по
умолчанию в браузере и наши страницы должны позволять это сделать. Таким
образом, мы можем установить основной размер шрифта в наборе правил для эле-
мента body документа, а затем масштабировать все шрифты относительно его.
К счастью, с помощью CSS сделать это очень просто. Для указания длины нужно
использовать единицы em:
body {
font-size: 100%; /* устанавливаем базовый размер шрифта для всего документа */
}
h1 {
font-size: xx-large; /* установлен относительно базового размера шрифта */
}
h2 {
font-size: x-large;
}
.important {
font-size: larger; /* делает шрифт несколько больше, чем в родительском элементе
*/
}
.onePointTwo {
font-size: 1.2em; /* устанавливает его в 1,2 раза больше базового размера */
}

В этом примере мы устанавливаем базовый размер шрифта равным 100 % раз-


мера, установленного в браузере пользователя, а затем масштабируем относитель-
но его остальные шрифты. Можно использовать абсолютные величины CSS хх-large
или x-large (а также аналогично x-small или xx-small), которые соответственно
будут изменять масштаб. Аналогично можем использовать относительные размеры,
larger или smaller, чтобы сделать размер шрифта больше или меньше для данного
содержания.
Если требуется более точно определенное управление размерами шрифтов, мы
используем величины em (что значит метаединицы) — множитель, на который
умножается текущий размер шрифта. Например, если базовый размер шрифта
элемента body равен, скажем, 12 пунктов, то установление для какого-то другого
элемента размера шрифта 1,5 em обеспечит точный размер 18 пунктов. Это очень
полезно, так как шрифты масштабируются соответственно базовому размеру шриф-
та. Благодаря этому мы можем поменять размер всех шрифтов на странице, изме-
няя размер одного только базового шрифта. Это значительно упрощает работу
с сайтом для слабовидящих людей: они часто устанавливают больший размер ба-
зового шрифта в браузере, используя таким образом em вместо абсолютных вели-
чин, что может нарушить разметку страницы.
Устранение браузерной несовместимости

В предыдущем примере мы установили размер шрифта в абзаце в 1,2 раза боль-


ше базового шрифта. На рис. 3.14 показано, как данный набор правил определяет
стили в документе.

Рис. 3.14. Примеры шрифтов на основе упомянутой ранее таблицы стилей

ß
С одним спорным инструментом, который называется сбросом CSS, вы будете
сталкиваться довольно часто.
Вспомните «плавающий» пример, когда мы убирали поля по умолчанию и отсту-
пы в тегах абзацев? Оказывается, разные браузеры имеют разные базовые настройки
CSS, в результате чего отображение стиля в этих браузерах может несколько разли-
чаться. Сброс CSS (CSS reset) был разработан для удаления всех браузерных настро-
ек по умолчанию. Самый известный инструмент создал Эрик Мейер.
Почему я назвал сброс CSS спорным? По нескольким причинам. Одна из них —
веб-доступность. Сброс настроек может, к примеру, вызывать проблемы у людей,
которые управляют навигацией с помощью клавиатуры. Еще одна причина — про-
изводительность. Поскольку в инструментах часто используется универсальный
селектор (*), они могут серьезно перегружать сайт. И наконец, эти инструменты
означают огромное количество лишней работы, потому что на деле довольно часто
браузерные настройки полностью соответствуют вашим нуждам.
В то же время я думаю, что сброс — отличный инструмент с точки зрения педа-
гогики, вот почему я рассказываю о нем здесь. Он заставляет новичков в точности
указывать, какими они хотят видеть отдельные аспекты страницы, а не полагаться
на значения в браузере по умолчанию. Поскольку я предполагаю, что вы являетесь
новичком в CSS, так как читаете сейчас эту книгу, то рекомендую вам поупраж-
няться со сбросами.
Чтобы сделать объяснение более понятным, я включу инструмент сброса в от-
дельную таблицу стилей и добавлю дополнительный элемент link в мой HTML.
Мы можем скопировать сброс Эрика Мейера в отдельный файл reset.css и связать
его с нашим HTML:
Глава 3. Стиль

<head>
<title>Пример 8 — Использование сброса CSS</title>
<link rel="stylesheet" type="text/css" href="reset.css">
<link rel="stylesheet" type="text/css" href="style.css">
</head>

Увидеть, к чему это приведет, очень просто. Рассмотрим следующий HTML:


<body>
<h1>Это заголовок</h1>
<h3>Это менее важный заголовок</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.</p>
</body>

На рис. 3.15 показано, как страница отображается в Chrome при применении


только стилей браузера.

Рис. 3.15. Простая страница со стилем по умолчанию

Обратите внимание на то, что шрифт и поля элементов h1 и h3 уже заданы.


У элемента p тоже изменены поля.
А сейчас сделаем сброс. Я скопировал свой файл сброса со страницы Эрика
Мейера и сохранил его в файле reset.css в той же самой папке, что и мой HTML.
Я также изменил тег <head> в HTML следующим образом:
Использование CSS Lint для выявления возможных проблем

<head>
<title>Пример 8 — Использование сброса CSS</title>
<link href="reset.css" rel="stylesheet" type="text/css">
</head>
Сейчас, перезагрузив страницу, мы увидим нечто похожее на рис. 3.16.

Рис. 3.16. Простая страница, где с помощью сброса удалены все стили по умолчанию

Поля, отступы и настройки шрифта по умолчанию были полностью удалены со


страницы. Конечно, мы не собираемся оставлять сайт в таком виде, но запомните
главное: включение сброса CSS полностью удаляет стили, использованные в брау­
зере по умолчанию. Благодаря этому те правила, которые мы разрабатываем и со-
храняем в своих CSS-файлах, всегда будут приводить к одному и тому же резуль-
тату, независимо от того, какие настройки (и, следовательно, соответствующий
набор правил по умолчанию) сделаны в браузере пользователя.

$&66/LQW’#’’
#
Как и в случае HTML, очень неплохо иметь инструмент, который поможет нам
выявлять потенциальные проблемы с CSS. Например, можете ли вы заметить
ошибку в этом примере?
body {
background: lightblue;
width: 855px;
margin: auto;
h1 {
background: black;
color: white;
}
p {
color: red;
margin: 10px
padding: 20px;
}
Глава 3. Стиль

Если вы нашли ее, получите тройку с плюсом, потому что в этом примере целых
две ошибки! Думаю, отсутствие закрывающей скобки после набора правил для body
бросается в глаза сразу. Но есть и еще одна.
Вы, наверное, нашли ее, посмотрев внимательно еще раз. Но если нет, обратите
внимание на набор правил для элемента p. После свойства margin пропущена точка
с запятой.
Как и HTML-валидатор, CSS Lint — это инструмент, который может помочь
выявить потенциальные проблемы с кодом. Как и онлайн-валидатор W3C HTML,
он не требует установки на компьютер какого-либо программного обеспечения.
Просто зайдите на страницу http://csslint.net (она показана на рис. 3.17), а затем
скопируйте и вставьте туда CSS.

Рис. 3.17. Домашняя страница CSS Lint

CSS Lint дает несколько больше предупреждений, чем инструмент для HTML,
изученный нами в главе 2, но здесь есть опции, позволяющие настроить уровень
предупреждений. Вы можете увидеть эти опции, щелкнув на стрелке вниз справа
на большой кнопке LINT! на главной странице. Опции разбиты на категории соглас-
но причинам предупреждений. Например, опция Disallow universal selector (Запрещать
универсальные селекторы) находится в категории Performance (Производитель-
ность). Это значит, что если вы готовы допустить чуть более медленную загрузку
вашей страницы ради какой-то особенной функциональности CSS, то отключаете
эту опцию и предупреждаете Lint об этом.
Обычно я оставляю все опции в CSS Lint выбранными, но вы можете настроить
и свои варианты. Я бы рекомендовал не выключать ни одну из них, а, получив
предупреждение, уделить немного времени поиску в Google, чтобы разобраться
Взаимодействие и решение проблем с Chrome Developer Tools

в причине. Ведь это почти то же самое, что получить консультацию у настоящего


эксперта по CSS!

Œ
&KURPH’HYHORSHU
7RROV
В абсолютно любом программном обеспечении очень часто наблюдается ситуация,
когда все идет совсем не так, как надо. При использовании CSS это чаще всего
означает, что страница выглядит в браузере совершенно иначе, чем вы ожидали,
а в чем проблема — непонятно. К счастью, браузер Chrome включает в себя отлич-
ный набор инструментов, которые помогут решить проблемы как с CSS, так
и с HTML.
Начнем с открытия файла margin_border_padding.html в Chrome. Затем откройте
меню View, выберите подменю Developer и щелкните на опции Developer Tools1. В ре-
зультате внизу страницы откроются Инструменты разработчика Chrome. Если вклад-
ка Elements вверху панели неактивна, щелкните на ней — таким образом вы увиди-
те нечто похожее на рис. 3.18.

Рис. 3.18. Инструменты разработчика Chrome, открытые для примера


margin_border_padding.html

1
На сегодняшний момент: кнопка панели Chrome в правом верхнем углуTools (Инст­
рументы)Developer Tools (Инструменты разработчика) или сочетание клавиш
Ctrl+Shift+I. — Примеч. пер.
Глава 3. Стиль

С левой стороны вы видите какой-то фрагмент HTML-кода со стрелочками,


которые могут сворачивать или разворачивать дочерние элементы. Если стрелка
направлена вправо, это значит, что вы можете щелкнуть на ней, чтобы показать
находящийся внутри нее HTML. Аналогично, если она направлена вниз, можете
щелкнуть на ней, чтобы его скрыть.
Наводя мышь на HTML-теги, вы увидите, что соответствующие им отобража-
емые элементы подсвечиваются на странице. Можете выбрать определенный эле-
мент HTML, щелкнув на его открывающем теге, и соответствующая ему строка
подсветится голубым.
HTML, который вы видите на вкладке Elements, может точно или не совсем
точно соответствовать HTML, который находится в вашем файле, но, если даже
совпадение неполное, это именно тот HTML-код, который отображает браузер. Как
я уже упоминал в главе 2, в некоторых случаях браузер совершенствует ваш код
самостоятельно (например, закрывая теги), и HTML, отображаемый на вкладке
Elements, содержит все предположения, сделанные Chrome. Например, если вы
создадите HTML-документ, содержащий только тег <p> без <html>, <header> и <body>,
Chrome допишет последние самостоятельно и покажет их вам на вкладке Elements
(рис. 3.19). Помните об этом, если когда-нибудь вас озадачит разница между тем,
что вы имели в виду по мнению Chrome, и тем, чего хотели на самом деле.

Рис. 3.19. Главный элемент div выделен, и вы можете видеть соответствующий ему набор правил

С правой стороны окна вы можете видеть CSS, связанный с выделенным HTML-


элементом. Попробуйте выделить элемент div, в котором находится абзац. Справа
на вкладке Styles вы увидите набор правил, связанный с этим элементом, а наведя
на него указатель мыши, можно отобразить флажки, появляющиеся слева от
Стилизуем Amazeriffic!

каждого из правил. Вы можете снять их, отменив таким образом какое-либо пра-
вило. Если возникли какие-то проблемы с вашим CSS, очень часто полезно по­
смотреть на эту вкладку, чтобы убедиться, что ожидаемые вами правила действи-
тельно применяются. Вы можете посмотреть, как правила отображаются на панели
разработчика Chrome, на рис. 3.19.
Но это еще не все! Вы можете напрямую манипулировать правилами, щелкнув
на каком-то из них и введя для него новое значение. Вы можете даже добавить
новые правила прямо здесь, но помните, что изменения не отразятся на вашем
CSS-файле. Так что, если вы найдете решение для применения стиля с помощью
этого окна, вам нужно вернуться в Sublime и включить этот стиль в CSS.
Вы, наверное, уже пробовали манипулировать с отступами, границами и поля-
ми, изменяя код. Попробуйте проделать аналогичные действия на вкладке Elements
на панели разработчика. Вы увидите, что намного проще попробовать что-то и тут
же наблюдать визуальный эффект. Но помните, что после обновления страницы
все изменения будут потеряны и Chrome снова будет отображать страницу, соот-
ветствующую вашим HTML- и CSS-файлам.

ø$PD]HULI¿F
А сейчас изучим все это на примере. В предыдущей главе мы составили структуру
для домашней страницы приложения, которое называлось Amazeriffic. Финальный
вариант HTML, определяющего структуру страницы, выглядел так:
<!doctype html>
<html>
<head>
<title>Amazeriffic</title>
</head>
<body>
<header>
<h1>Amazeriffic</h1>
<nav>
<a href="#">Войти</a> |
<a href="#">ВиО</a> |
<a href="#">Техподдержка</a>
</nav>
</header>
<main>
<h2>Amazeriffic изменит вашу жизнь!</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<h3>Почему вам нужен Amazeriffic?</h3>
<ul>
<li>Он легко впишется в вашу жизнь</li>
<li>Он классный</li>
<li>Он перевернет ваш мир</li>
</ul>
<img src="lightbulb.png" alt="Изображение лампочки">
Глава 3. Стиль

</main>
<footer>
<div class="contact">
<h5>Свяжитесь с нами</h5>
<p>Amazeriffic!</p>
<p>555 50-я улица</p>
<p>Эшвилл, Северная Каролина 28801</p>
</div>
<div class="sitemap">
<h5>Карта сайта</h5>
<ul>
<li><a href="#">Домой</a></li>
<li><a href="#">О нас</a></li>
<li><a href="#">Право</a></li>
<li><a href="#">Техподдержка</a></li>
<li><a href="#">ВиО</a></li>
<li><a href="#">Вакансии</a></li>
</ul>
</div>
</footer>
</body>
</html>
Сначала мы должны зайти в каталог, где располагается проект Git для Amazeriffic.
Если мы находимся в домашней папке, то можем перейти прямо в каталог Amazeriffic
с помощью команды cd и указания полного пути в качестве аргумента:
hostname $ cd Projects/Amazeriffic
Можем использовать ls, чтобы увидеть содержимое папки, а затем выполнить
git log, чтобы увидеть историю коммитов, созданную в главе 2. Моя выглядит при-
мерно так:
hostname $ ls
index.html
hostname $ git log
commit efeb5a9a5f80d861119f5761df789f6bde0cda4f
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 1:41:52 2013 -0400
Add content and structure to footer
commit 09a6ea9730521ed1effd135a243723a2745d3dc5
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 12:32:17 2013 -0400
Add content to main section
commit f90c9a6bd896d1a303f6c3647a7475d6de9c4f9e
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 11:45:21 2013 -0400
Add content to header section
commit 1c808e2752d824d815929cb7c170a04267416c04
Author: Semmy Purewal <semmy@semmy.me>
Date: Thu May 23 10:36:47 2013 -0400
Add skeleton of structure to Amazeriffic
commit 147deb5dbb3c935525f351a1154b35cb5b2af824
Стилизуем Amazeriffic!

Author: Semmy Purewal <semmy@semmy.me>


Date: Thu May 23 10:35:43 2013 -0400
Initial commit1
Затем мы можем открыть папку и создать два новых CSS-файла. Сначала со-
здадим файл reset.css, основанный на сбросе HTML Эрика Мейера. Вы можете
набрать код (как описано ранее) или скопировать его с сайта Эрика. Создадим
также файл style.css, где будет находиться наш собственный CSS.
Прежде чем отправить коммит, сразу свяжем наши CSS-файлы с файлом HTML,
добавив в заголовок две ссылки:
<head>
<title>Amazeriffic</title>
<link rel="stylesheet" type="text/css" href="reset.css">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
Сейчас мы можем открыть этот файл в Chrome. Он будет полностью лишен
стилей из-за наличия файла сброса. Мой выглядит так, как показано на рис. 3.20.

Рис. 3.20. Финальный HTML Amazeriffic, включающий сброс CSS

1
Сообщения о коммитах сверху вниз: «Добавлены содержание и структура подвала»,
«Добавлено содержание секции main», «Добавлено содержание секции header», «Добавлен
скелет структуры Amazeriffic», «Начальный коммит». — Примеч. пер.
Глава 3. Стиль

А сейчас отправим изменения в хранилище Git:


hostname $ git add reset.css
hostname $ git add style.css
hostname $ git add index.html
hostname $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
# new file: reset.css
# new file: style.css
#
hostname $ git commit -m "Add reset.css and style.css, link in index.html"
[master b9d4bc9] Add reset.cssand style.css, link in index.html
3 files changed, 142 insertions(+), 2 deletions(-)
create mode 100644 reset.css
create mode 100644 style.css

ø
Исследуем стиль для Amazeriffic. Как вы можете видеть, содержимое страницы
разделено на две колонки. Первая занимает около двух третей области содержания,
а вторая — только одну треть. Затем обратите внимание на то, что содержание
разбито по вертикали примерно по три строки. Это определяет стилистическую
сетку для содержания, которая является одним из базовых подходов при разработ-
ке компоновки страницы.
Еще один аспект, который нелегко продемонстрировать в книге, — это дизайн
фиксированной ширины. Это значит, что при изменении ширины окна браузера
содержимое страницы остается выровненным по центру и сохраняет горизонталь-
ный размер. На рис. 3.21 и 3.22 вы можете видеть Amazeriffiic в окнах браузера
разного размера.
Это значит, что его дизайн не является адаптивным — так называется современный
подход к верстке CSS, обеспечивающий перестроение макета в зависимости от шири-
ны окна браузера. Это важно при веб-разработке, так как многие люди будут просмат-
ривать ваш сайт на мобильном телефоне или планшете. Мы не будем заниматься этим
сейчас, но вы можете испробовать несколько инструментов, которые упрощают раз-
работку адаптивного дизайна, выполнив упражнения в конце этой главы.
Наша цель — создать фиксированный дизайн в две колонки с помощью CSS.
Для начала нужно, чтобы строки (в данном случае это элементы header, main и footer)
имели фиксированную ширину — значение свойства width. Первое, что необходи-
мо сделать, — создать наборы правил для этих элементов. Добавим в файл style.css
набор правил, который выглядит следующим образом:
header, main, footer {
min-width: 855px;
}
Стилизуем Amazeriffic!

Рис. 3.21. Amazeriffic в окне браузера 1250 × 650

Рис. 3.22. Amazeriffic в окне браузера 800 × 575


Глава 3. Стиль

Свойство min-width означает, что, если ширина окна браузера становится


равной 855 пикселам или меньше, остальная часть страницы будет скрыта. Про-
верьте это.
Затем обратите внимание на то, что область подвала — желтого цвета. Он
заполняет нижнюю часть страницы по мере того, как окно браузера вытягива-
ется вертикально. Это значит, что желтый цвет должен быть фоновым цветом
всей страницы, но нам придется изменить цвета элементов header и main позд-
нее.
Мы можем добавить правило для body, которое устанавливает фоновое изобра-
жение желтого цвета. Отмечу, что, хотя и установил предыдущий набор правил
первым, я добавил набор правил для body в файл style.css выше его. Это объясня-
ется тем, что body расположен в HTML-файле до элементов header, main, footer. Если
сохранять этот порядок, становится проще найти проблемы и произвести измене-
ния:
body {
background: #f9e933;
}
header, main, footer {
min-width: 855px;
}

Сейчас мы видим, что вся страница стала желтого цвета, так что нужно изме-
нить фоновый цвет элементов header и main на белый. Чтобы сделать это, мы
должны добавить два новых набора правил для элементов header и main в допол-
нение к тому, который содержит свойство min-width. Разделим их, так как далее
придется добавить отдельные стилистические правила в каждый набор, как мы
увидим позднее:
header {
background: white;
}
main {
background: white;
}

В результате сейчас мы можем увидеть нечто похожее на рис. 3.23.

На момент написания книги тег <main> может отображаться в браузере Safari не совсем
корректно. Если вы столкнулись с проблемами при использовании этого браузера, по­
пробуйте добавить набор правил для элемента main, включающий правило display:
block;.

А сейчас, когда у нас уже заданы какие-то базовые стили, было бы неплохо до-
бавить эти изменения в хранилище Git. Начните с проверки статуса. Единственный
поменявшийся файл — style.css. Добавьте этот файл для фиксации с помощью
команды add, а затем отправьте коммит с поясняющим сообщением.
Стилизуем Amazeriffic!

Рис. 3.23. Amazeriffic c (очень) начальными стилями

Чего нам хотелось бы дальше? Мы собирались выровнять содержимое страни-


цы по центру и зафиксировать ширину 855 пикселов. Это значение объясняется
тем, что нам требуется число, делимое на 3 (так как колонки имеют соотношение
2:1 по ширине). Поскольку 855 / 3 = 285, это дает возможность работать с целыми
числами.
Вот как можно решить эту задачку. Мы можем добавить правило max-width в на-
бор стилей main, header и footer, установить ширину элемента body 855 пикселов,
а затем установить для поля элемента body значение auto. Прежде чем двигаться
дальше, попробуйте сделать это и посмотрите, что получится.
Если вы действительно попробовали, то, наверное, заметили, что области заго-
ловка и основной части сейчас не растягиваются на полную ширину страницы,
из-за чего цвет фона заполняет области слева и справа. Это не совсем то, что нам
нужно.
Как это исправить? Сейчас как раз тот случай, когда нужно добавить в DOM
элемент div, чтобы задавать стили элементов независимо друг от друга. Мы хотим,
чтобы элементы header и main занимали всю ширину страницы, но их содержимое
оставалось в пределах 855 пикселов. Модифицируем HTML так, чтобы в элементах
Глава 3. Стиль

header, main и footer содержался класс под названием conteiner, где находились бы
элементы содержания. Например, вот какой div мы можем сделать для элемента
header:
<header>
<div class="container">
<h1>Amazeriffic</h1>
<nav>
<a href="#">Войти</a> |
<a href="#">ВиО</a> |
<a href="#">Техподдержка</a>
</nav>
</div>
</header>
Таким образом, сейчас мы можем задать для этого контейнера максимальную
ширину 855 пикселов и автоматическую ширину полей. Когда закончим, набор
правил будет выглядеть так:
body {
background: #f9e933;
}
header, main, footer {
min-width: 855px;
}
header .container, main .container, footer .container {
max-width: 855px;
margin: auto;
}
header {
background: white;
}
main {
background: white;
}
Сейчас, если мы изменим ширину окна браузера, ширина содержимого стра-
ницы окажется неизменной! Так что в этот момент очень полезно будет вернуть-
ся к командной строке и отправить изменения в хранилище Git. В этот раз мы
изменили два файла, поэтому оба нужно добавить в хранилище Git перед комми-
том.

ø
Следующее, что нам нужно, — создать колонки для содержимого элементов. Чтобы
сделать это, прикрепим содержание, которое должно находиться справа, к правому
краю страницы и укажем значение ширины для правой колонки 285 пикселов, а для
левой — 570 пикселов. Добавим также свойство overflow со значением auto, чтобы
элемент не сжимался до минимума, когда мы установим свойство flow для обоих
дочерних элементов.
Стилизуем Amazeriffic!

Закончив редактировать наборы правил, мы увидим нечто вроде этого:


header {
background: white;
overflow: auto;
}
header h1 {
float: left;
width: 570px;
}
header nav {
float: right;
width: 285px;
}
Элемент header несколько выделяется из-за того, что содержит только два до-
черних элемента — h1 и nav. Но для элементов footer и main это не так. Если вы
понимаете все правильно, то, наверное, думаете, что стоило бы добавить два допол-
нительных элемента div к ним обоим, чтобы разбить плавающие левую и правую
части на два отдельных элемента. А в правой части подвала нужно будет создать
еще одну подразметку для оформления карты сайта. Все это соответствует дизай-
ну — и вы должны быть способны сделать это, изучив материал этой главы.
Я бы создавал какую-то одну секцию за один раз: добился верного вида секции
header, затем — основной области, а затем и подвала. Вы можете каждый раз делать
коммиты в хранилище Git. Я бы рекомендовал также периодически прогонять код
через CSS Lint (и HTML-валидатор, если вы также редактируете HTML), чтобы
убедиться в отсутствии потенциальных проблем.
Закончив с этими секциями, вы увидите нечто похожее на рис. 3.24.

Рис. 3.24. Более стильный Amazeriffic


Глава 3. Стиль

Если страница выглядит так же, как на рисунке, и вы выявили все возможные
проблемы, пропустив CSS через сервис CSS Lint, то лучше всего сейчас сделать
новый коммит в хранилище Git.


Следующим шагом будет добавление двух шрифтов Google. Для этого примера я ис-
пользовал шрифты Ubuntu и Droid Sans. Все теги заголовков (<h1>, <h2>, <h3> и <h4>)
выводятся шрифтом Ubuntu, а остальная часть контента — шрифтом Droid Sans.
Для этого нам понадобится добавить два тега link в HTML-файл и несколько пра-
вил — в CSS.
После установления шрифтов отправьте изменения в хранилище Git. Сделав
это, установите базовый размер шрифта 100 %, а затем модифицируйте размеры
шрифтов элементов так, чтобы они наиболее точно соответствовали оригинально-
му макету, приведенному на иллюстрации.
После того как размеры шрифтов будут выставлены корректно, провалидируй-
те HTML и пропустите CSS через сервис Lint, чтобы установить возможные про-
блемы. Сделав это, отправьте изменения в хранилище Git.

!$
И наконец, нужно добавить поля и отступы, чтобы элементы отделялись друг от
друга на странице. Это потребует экспериментов с полями и отступами для разных
элементов. Я рекомендую проделать это с помощью Chrome Developer Tools. Вы
также должны задать стили для имеющихся на странице ссылок. Все ссылки крас-
ные и не подчеркиваются, пока на них не наведен курсор. Это потребует от вас
манипуляций с псевдоклассом hover для элементов a.

В этой главе мы использовали CSS, чтобы добавить стили к структуре пользова-


тельского интерфейса и содержания. Основная идея CSS состоит в том, что мы
связываем стилистические наборы правил с элементами DOM. Большинство из
этих наборов правил наследуются: если мы добавляем набор правил для какого-
либо элемента DOM, то они применяются также для всех его дочерних элементов,
кроме случаев перекрытия набором правил, указанным для какого-либо опреде-
ленного элемента. В случаях неопределенности работает принцип каскадных пра-
вил.
Вы можете указать наборы правил CSS строго для определенных элементов
DOM с помощью CSS-селекторов. Работа с селекторами для отдельных элементов
или их групп в DOM требует некоторой практики.
Мы можем выполнить разметку основы документа по сетке с помощью свойств
CSS float. Можем закреплять элементы CSS с правого или левого края, извлекая
Больше теории и практики

их из обычного последовательного построения. Если у элемента есть два дочерних


элемента и мы закрепляем один дочерний элемент слева, а другой — справа, они
будут отображаться рядом, если не перекрываются. Мы можем добиться этого,
правильно указав ширину элементов.
CSS Lint — инструмент, который помогает нам избежать случайных ошибок
или опечаток в CSS. Кроме того, богатый набор интерактивных инструментов
встроен в Chrome.

Ø$  
В Amazeriffic осталось еще несколько проблем, которые нужно исправить. Я реко-
мендую вам сделать это, чтобы добиться наиболее полного совпадения с макетом.

Добавим еще несколько шагов в упражнения. Помните, что ваша цель — запомнить
эти шаги вместе с теми, которые перечислены в предыдущих главах.
1. Создайте простой файл style.css, в котором с помощью универсального селек-
тора обнуляются поля и отступы для всех элементов, а фон страницы установ-
лен светло-серым.
2. Свяжите файл style.css с вашим HTML-файлом в теге <head> и удостоверьтесь,
что все корректно работает, обновив страницу.
3. Зафиксируйте изменения файлов style.css и index.html в хранилище Git.
4. Добавьте некоторые базовые стилевые правила для элементов header, main
и footer и удостоверьтесь, что все корректно работает, обновив страницу.
5. Зафиксируйте изменения в новом коммите.

ß’&66
Наберите следующий документ HTML и сохраните его в файле с названием
selectorpractice.html:
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hi</h1>
<h2 class="important">Снова привет</h2>
<p class="a">Случайный несвязанный абзац</p>
<div class="relevant">
<p class="a">первый</p>
<p class="a">второй</p>
<p>третий</p>
Глава 3. Стиль

<p>четвертый</p>
<p class="a">пятый</p>
<p class="a">шестой</p>
<p>seventh</p>
</div>
</body>
</html>

Затем присоедините к нему файл CSS с помощью тега link. В CSS добавьте
единственный селектор:
* {
color: red;
}

Это изменит цвет текста всех элементов на красный. Мы используем этот файл,
чтобы практиковаться в применении CSS-селекторов. Заменяйте символ * селек-
тором для элемента из следующего списка и проверяйте, что красными становятся
только выбранные элементы:
 элемент <h1>;
 элемент <h2> через его класс;
 все абзацы класса relevant;
 первый абзац из relevant;
 третий абзац из relevant;
 седьмой абзац из relevant;
 все абзацы на странице;
 только случайный несвязанный абзац;
 все абзацы класса а;
 только абзацы relevant класса a;
 второй и четвертый абзацы (подсказка: используйте запятую).
В документации разработчика Mozilla (Mozilla developer documentation) содер-
жится великолепный обучающий материал по селекторам. Этот учебник покры-
вает намного больше, чем вы изучили здесь. Как только вы как следует усвоите
селекторы, с которыми я вас познакомил, очень рекомендую почитать этот мате-
риал.

’#Œ’$PD]HULI¿F
Если вы выполнили задания в конце главы 2, то у вас есть страница ВиО (FAQ)
для Amazeriffic. Свяжите файл style.css со страницей faq.html и добавьте допол-
нительные стилевые правила, чтобы задать стили для области содержания на этой
странице. Если структура заголовка и подвала такая же, то вам не придется редак-
тировать эти правила. Чтобы задать стили для страницы ВиО, нужно будет просто
добавить дополнительные правила для секции main.
Больше теории и практики

#
В этой главе я осветил каскадные правила CSS довольно поверхностно. Я думаю,
в большинстве случаев они вполне понятны интуитивно. В то же время знание
правил в деталях очень часто оказывается полезным при решении проблем с CSS.
Принципы четко определены в стандарте W3C. Я рекомендую вам прочесть их и хо-
рошенько запомнить, особенно если придется много работать с CSS.

Ł$
Обширная тема в CSS, которую мы не изучили, — адаптивный дизайн. Дизайн
является адаптивным, если он изменяется в зависимости от высоты и ширины окна
браузера, в котором отображается страница. Это очень важно в наши дни, когда
огромное количество людей просматривает веб-приложения и веб-страницы с по-
мощью мобильных телефонов и планшетов.
Адаптивность CSS можно обеспечить с помощью медиазапросов, но рассмот-
рение этой темы несколько выходит за рамки книги. Вы можете прочитать о них
на сайте Mozilla для разработчиков. Я настоятельно рекомендую сделать это.
Кроме того, некоторые CSS-фреймворки предоставляют возможность постро-
ения адаптивного дизайна с минимальными усилиями. Я использую как Twitter
Bootstrap, так и Zurb’s Foundation. Если вам понравилось работать с компоновкой
и CSS в этой главе, то советую прочесть документацию к обоим фреймворкам
и попробовать сделать дизайн Amazeriffic адаптивным с помощью одного из них.
Помните об этих фреймворках как о стартовых точках для будущих проектов.
$

Итак, мы изучили три очень важных аспекта разработки веб-приложений. К сожа-


лению, мы пока что не создали приложения, которое что-нибудь делает. Мы про­сто
задали структуру его содержания и привлекательно оформили.
В этой главе мы с вами начнем исследование интерактивности. Это позволит
нам наконец уйти от построения статичных веб-страниц — мы начнем создавать
динамические приложения, реагирующие на действия пользователя.
К концу этой главы мы кое-что узнаем о JavaScript — сценарном языке програм-
мирования, который работает внутри каждого браузера, и jQuery — библиотеке
JavaScript, которая очень полезна для управления элементами DOM (помимо всего
прочего). Кроме того, уделим некоторое время изучению основ языка JavaScript,
обратив основное внимание на использование массивов и управление ими.

-DYD6FULSW
Начнем с примера. Как и прежде, создадим в папке Projects каталог под названием
Chapter4. В нем сделаем еще один, Example1, и откроем его в Sublime. Затем создадим
три файла: index.html, style.css и app.js. Начнем с index.html:
<!doctype html>
<html>
<head>
<title>Название приложения</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h1>Название приложения</h1>
<p>Описание приложения</p>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="app.js"></script>
</body>
</html>

Все в этом файле должно быть вам знакомо, за исключением тегов <script> в ниж-
ней части элемента body. Как и теги link, элементы script позволяют вам связывать
Привет, JavaScript!

внешние файлы с вашим HTML, но script, как правило, обращаются к файлам


JavaScript. И еще одна важная особенность: в отличие от тега <link>, для <script>
обязателен закрывающий тег.
Мы поместили теги <script> в body, а не в head по технической причине: браузер
отображает страницу сверху вниз, создавая DOM-элементы по мере продвижения
по HTML-документу. Если мы помещаем теги <script> в конце, то файлы JavaScript
будут открыты одними из последних. Поскольку эти файлы часто довольно боль-
шие и требуют некоторого времени на загрузку, лучше открывать их в последнюю
очередь, чтобы как можно быстрее отобразить для пользователя визуальные эле-
менты.
Первый тег <script> включает широко используемую библиотеку под названи-
ем jQuery, которую мы подробно изучим в последующих секциях. Тег открывает
jQuery с http://code.jquery.com — сети доставки контента. Это значит, что нет не-
обходимости хранить копию jQuery на локальной машине. Вместо этого мы ука-
зываем браузеру загрузить ее с внешнего сайта (аналогичным образом мы обраба-
тывали Google Fonts в предыдущей главе). Так что ваш компьютер должен быть
подключен к Интернету, чтобы этот пример работал правильно.
Второй элемент script включает сценарий app.js, который содержит написанный
нами код JavaScript. Мы скоро создадим этот файл.
У нас есть также связанный файл CSS, в котором уберем поля и отступы, заданные
по умолчанию, для всех элементов на странице. Использование универсального се-
лектора вызовет предупреждение от CSS Lint, но мы пока проигнорируем его.
* {
margin: 0;
padding: 0;
}

Файл app.js наиболее интересен для нас. Его содержание показано в следующем
фрагменте кода. Прежде чем мы поговорим о нем, наберите этот код в точности как
в книге, а затем откройте файл index.html в браузере.
var main = function () {
"use strict";
window.alert("hello, world!");
};
$(document).ready(main);

Если вы все набрали правильно, то должны увидеть оповещение JavaScript с тек-


стом «hello, world!» при загрузке страницы (рис. 4.1).
Это та самая база, которую мы будем использовать во всех наших программах
JavaScript. Два ключевых момента, которые мы и будем рассматривать в остальной
части книги, посвященной клиентской стороне, заключаются в следующем:
 определяется глобальная функция, называемая main, которая является входной
точкой выполнения программы;
 для выполнения функции main используется jQuery после того, как документ
HTML полностью загрузился и готов к использованию.
Глава 4. Интерактивность

Рис. 4.1. Наше первое интерактивное приложение

Код настроен на запуск в строгом режиме (strict), который запрещает исполне-


ние некоторых неудачных аспектов JavaScript, вызывавших проблемы у разработ-
чиков в прошлом. В нашем коде это всегда будет первой строкой в функции main.
Сценарий также создает сообщение «Hello, world!» — просто для примера.

А сейчас создадим с помощью JavaScript что-нибудь поинтереснее. Наша цель — со-


здание простого приложения, позволяющего пользователю ввести какой-то текст,
который затем появится на странице в другом месте. Этот пример продемонстри-
рует, как просто можно создавать интересные вещи с использованием всего не-
скольких строк кода.
Начнем с создания новой папки Example2 в каталоге Chapter4. Создайте пустое
хранилище Git и откройте папку в Sublime. Создайте заново три файла из примера,
приведенного в предыдущем разделе (index.html, style.css и app.js). Как мы уже
обсуждали в предыдущих главах, полезно запомнить эту основную структуру,
чтобы вы могли создавать ее сразу, без необходимости копировать код.
Как только у вас появилась основа проекта, выполните начальный коммит
в хранилище Git.

ø
Посмотрим, к какому пользовательскому интерфейсу нам нужно стремиться. Он
показан на рис. 4.2.
Первое интерактивное приложение

Рис. 4.2. Пользовательский интерфейс нашего интерактивного приложения

Вы видите, что структура состоит из трех основных элементов: заголовка header,


подвала footer и основной части main. Помня об этом, можем модифицировать
HTML для отображения этого интерфейса:
<!doctype html>
<html>
<head>
<title>Простое приложение</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<header>
</header>
<main>
<!-- обратите внимание: здесь для названия класса css, содержащего
несколько слов, мы используем тире, это соглашение CSS -->
<section class="comment-input">
</section>
<section class="comments">
</section>
</main>
<footer>
</footer>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="app.js"></script>
</body>
</html>

Как только вы соответствующим образом изменили HTML, отправьте его в хра-


нилище Git. Затем заполните элементы header и footer вместе с основной частью
Глава 4. Интерактивность

содержания main с левой стороны в соответствии с рис. 4.2. Вы, наверное, заметили,


что некоторые из этих блоков будут содержать теги <h1>, <h2> и <h3> с определенным
содержимым. Добавьте их сейчас.
В блоке comment-input нам понадобится новый тег HTML <input>. Элемент input
представляет собой текстовое поле ввода и не требует закрывающего тега. Добавь-
те пустой тег <input> в блок comment-input. Вы можете также добавить абзац с под-
сказкой «Добавьте ваш комментарий»:
<section class="comment-input">
<p>Добавьте ваш комментарий:</p>
<input type="text">
</section>

В блоке .comments добавьте несколько комментариев — просто в тегах абзацев.


Это поможет в ходе присвоения стилей. Например, мы можем модифицировать
секцию с комментариями примерно так:
<section class="comments">
<p>Это первый комментарий!</p>
<p>Это второй!</p>
<p>Это следующий.</p>
<p>А это еще один!</p>
</section>

И наконец, нужно добавить элемент button — кнопку с пометкой +. Вы еще не


видели его раньше, но этот элемент бесполезен, пока мы не добавим для него код
JavaScript. Чтобы добавить кнопку, вставим тег <button> непосредственно за тегом
<input>. Код, находящийся в моей секции comment-input, выглядит так:
<section class="comment-input">
<p>Add Your Comment:</p>
<input type="text"><button>+</button>
</section>

Закончив работать со структурой, отправим ее в хранилище Git, а затем перей-


дем к заданию стилей на странице.

ø$
Оформление будет совсем несложным. Полезно будет использовать сброс CSS, но
это не обязательно для данного примера. Но как минимум вы должны создать
простую стартовую точку для CSS, обнулив поля и отступы.
Ширина основной части в данном примере — 855 пикселов, а левая колонка
занимает одну треть ширины. Другие две трети содержат секцию комментариев.
Один интересный аспект секции комментариев — меняющиеся цвета. Другими
словами, фон четных комментариев — одного цвета, а нечетных — другого. Мы легко
можем достичь этого эффекта с помощью CSS — просто добавим псевдокласс ntn-child
со значениями even (четные) и odd (нечетные). Например, если мы хотим, чтобы че-
редовались цвета lavender и gainsboro, то можем создать вот такие правила CSS:
Первое интерактивное приложение

.comments p:nth-child(even) {
background: lavender;
}
.comments p:nth-child(odd) {
background: gainsboro;
}
Если вы добавили несколько комментариев для примера, то можете увидеть,
что цвет их фона меняется в соответствии с новыми правилами CSS. Обратите
внимание на то, что мы также добавили для комментариев свойство border-radius,
чтобы уголки фоновой подложки были слегка закругленными.
Кроме этого, и остальная часть оформления должна соответствовать макету как
можно точнее. Закончите присваивать стили интерфейсу настолько хорошо, на-
сколько сможете, и отправьте код в хранилище Git, чтобы мы могли перейти к ин-
терактивности. Если столкнетесь с трудностями, посмотрите на примеры, находя-
щиеся в хранилище Git.

$
А сейчас мы с вами готовы работать с интерактивностью. Построим наш пример
шаг за шагом, по ходу дела выполняя коммиты в Git.

!#
Начнем со следующего: когда пользователь нажимает кнопку +, мы вставляем
новый комментарий в секцию комментариев. Для этого изменим код app.js вот
так:
var main = function () {
"use strict";
$(".comment-input button").on("click", function (event) {
console.log("Hello, World!");
});
};
$(document).ready(main);
Загрузите вашу страницу в браузере и откройте инструменты разработчика
Chrome, как описано в предыдущей главе. После этого щелкните на вкладке Console
(Консоль) на верхней панели инструментов разработчика. Сейчас, щелкнув на
кнопке +, вы увидите надпись «Hello, World!», появляющуюся в консоли. Если все
настроено правильно и работает корректно, это отличный момент для того, чтобы
отправить вашу работу в хранилище Git.
Что же здесь происходит? Код связывает слушатель событий с элементом DOM,
указанным в вызове функции $. Обратите внимание на то, что аргумент функции
$ очень похож на селектор CSS — и не случайно! Именно так и обстоит дело. jQuery
позволяет вам легко обращаться к элементам DOM с помощью селекторов CSS,
а затем управлять ими, используя JavaScript.
Событие, которое мы ожидаем на элементе button, — это щелчок кнопкой мыши.
Слушатель сам по себе — это функция, которая просто выводит «Hello, World!»
Глава 4. Интерактивность

в консоли. Так что на естественном языке мы можем пересказать этот код следующим
образом: когда пользователь нажимает кнопку +, набирай «Hello, World!» в консоли.
Конечно же, на самом деле нам вовсе не нужно вводить «Hello, World!» в кон-
соли — мы хотим добавить комментарий в секцию комментариев. Чтобы прийти
к этому, мы должны немного модифицировать наш код, заменив console.log другой
строкой jQuery, которая добавляет элемент DOM в раздел комментариев:
var main = function () {
"use strict";
$(".comment-input button").on("click", function (event) {
$(".comments").append("<p>Это новый комментарий</p>");
});
};
$(document).ready(main);

Обновите свою страницу в браузере, и, если все хорошо, вы должны увидеть,


что в секции комментариев появился новый: «Это новый комментарий». Вы заме-
тите, что код jQuery (начинающийся с $) выбирает секцию комментариев таким же
образом, как и мы выбирали ее в файле CSS, а затем применяет к ней какой-то
HTML-код. Если же это не работает, вернитесь обратно и убедитесь в том, что вы
все набрали правильно.

%’20
Мы значительно продвинулись к нашей цели, но пока щелчок кнопкой мыши на
кнопке приводит к появлению одного и того же комментария. А нужно изменять
текст в тегах абзаца на тот, что находится в поле ввода. Начнем с создания пере-
менной для хранения элемента DOM, который мы собираемся добавить:
var main = function () {
"use strict";
$(".comment-input button").on("click", function (event) {
var $new_comment = $("<p>");
$new_comment.text("Это новый комментарий");
$(".comments").append($new_comment);
});
};
$(document).ready(main);

В результате этой модификации не изменилось ничего, что делает наш код, но


сам код реструктурирован: теперь настраивать текст в тегах абзаца куда проще.
В частности, мы добавили объявление и назначение переменной. Имя переменной
$new_comment может быть любым, каким хотите, но если в ней будет храниться объект
jQuery, разумно было бы выделять ее, используя $ в качестве первого символа.
Первая строка нового кода создает новый элемент абзаца как объект jQuery,
а вторая изменяет текстовое содержание этого абзаца на «Это новый комментарий».
Поскольку jQuery позволяет объединять вызовы функций в цепочки, мы можем,
если так удобнее, сделать из двух строк одну:
var $new_comment = $("<p>").text("this is a new comment");
Первое интерактивное приложение

Но даже если мы используем в jQuery цепочку, важно помнить, что здесь про-
исходят две вещи — создается новый элемент абзаца, а его текстовое содержание
заменяется на «Это новый комментарий».
Затем нам нужно сделать так, чтобы содержимое текстового поля ввода храни-
лось в переменной, которую мы создали. Перед этим, однако, остановимся на том,
как определить элемент input, находящийся в секции .comment-input в jQuery.
Можно попробовать сделать это, используя селекторы CSS! Вот решение:
$(".comment-input input");

Как и в CSS, эта строка ищет элементы класса .comment-input, а затем просмат-
ривает их дочерние элементы в поисках элемента input. Поскольку только один
элемент отвечает этим критериям, он и будет выбран в результате.
Итак, теперь мы знаем, что можем получить содержимое извне. Оказывается,
что в jQuery есть функция, позволяющая вернуть содержимое текстового поля
ввода. Она называется val — сокращение от value («значение»). Мы можем получить
доступ к содержимому текстового поля, использовав вот такой код:
$(".comment-input input").val();

А сейчас надо просто его обработать! В данном случае необходимо, чтобы со-
держимое текстового поля ввода стало текстовым содержанием нового элемента
абзаца. Следовательно, нужно реструктурировать код примерно так:
var main = function () {
"use strict";
$(".comment-input button").on("click", function (event) {
var $new_comment = $("<p>"),
comment_text = $(".comment-input input").val();
$new_comment.text(comment_text);
$(".comments").append($new_comment);
});
};
$(document).ready(main);

Если нужно, мы можем поместить все это в одну строку без использования
промежуточной переменной для хранения результата вызова функции val:
var $new_comment = $("<p>").text($(".comment-input input").val());

А сейчас откройте приложение в вашем браузере. Если все хорошо, вы сможете


набрать какой-либо текст в текстовом поле, щелкнуть на кнопке и увидеть, что
набранный текст добавлен в секцию комментариев. Если что-то не работает, вы
можете проверить код еще раз, чтобы удостовериться, что все двоеточия и скобки
на своих местах, а затем открыть Инструменты разработчика Chrome и проверить, нет
ли каких-либо ошибок в консоли. Сообщение об ошибке может намекнуть вам, где
именно что-то не так.
Как только вы добились корректной работы приложения, отправьте изменения
в хранилище Git с поясняющим комментарием.
Глава 4. Интерактивность

В данной точке все должно работать правильно. Для пользователей, однако,


приложение оставляет желать лучшего, и мы можем внести несколько мелких
изменений, которые сделают работу куда более приятной.

Вы, вероятно, не подозреваете, что в нашем коде уже есть баги, но, как ни странно,
это именно так! Полезно будет уделить некоторое время продумыванию ожида­емого
поведения нашего приложения и проверке того, соответствует ли ему действитель-
ное положение вещей.
Если вы все еще его не нашли, вот подсказка: когда мы щелкаем на кнопке до-
бавления при отсутствии текста в поле ввода, программа jQuery добавляет в DOM
пустой элемент p. Из-за этого нарушается цветовая схема четных/нечетных эле-
ментов, которую мы разработали в CSS.
Чтобы увидеть, как баг делает свое черное дело, прежде всего перезагрузите стра-
ницу. Затем напишите один комментарий с помощью поля ввода, очистите поле
ввода, щелкните на кнопке добавления, а затем добавьте еще один комментарий. Если
вы в точности следовали этим инструкциям, то увидите, что два комментария подряд
отображаются с одним и тем же цветом фона! Это потому, что пустой комментарий,
который не отображается, должен был появиться на фоне «нечетного» цвета.
Мы можем убедиться, что баг проявляется именно из-за пустого элемента р,
также с помощью вкладки Elements инструментов разработчика Chrome. Начните
с открытия инструментов разработчика Chrome и перехода к вкладке Elements.
Затем перейдите от элемента main к секции комментариев. Имея перед глазами эту
секцию, вы можете сейчас нажать кнопку добавления, не написав никакого ком-
ментария в поле ввода, и увидите, что был добавлен пустой элемент р.
Что же делать? Вообще говоря, следует перед добавлением комментария вы-
полнять проверку того, есть ли какое-либо содержимое в поле ввода. Мы можем
сделать это с помощью оператора if:
$(".comment-input button").on("click", function (event) {
var $new_comment;
if ($(".comment-input input").val() !== "") {
$new_comment = $("<p>").text($(".comment-input input").val());
$(".comments").append($new_comment);
}
})

Сочетание !== подтверждает, что содержимое поля ввода не должно быть пустой
строкой, так что, по сути, это и есть проверка того, что поле ввода не пустое. Таким
образом, условие if допускает выполнение кода, добавляющего новый комментарий,
только в том случае, если поле ввода не пустое. Так простое изменение позволило
нам исправить баг, и сейчас было бы неплохо отправить коммит в хранилище Git.


Еще одна серьезная проблема пользовательского взаимодействия — то, что поле
ввода не очищается после того, как пользователь щелкнет на кнопке. Если он хочет
Первое интерактивное приложение

добавить еще один комментарий, то вынужден вручную удалять текст, добавленный


предварительно. Очистить поле ввода очень просто: мы обращаемся к объекту
в jQuery с помощью метода val, непосредственно указывая его значение, поле вво-
да примет это значение. Иными словами, мы можем очистить текущее содержание
поля, отправив в него пустую строку с помощью метода val:
$(".comment-input input").val("");
Чтобы реализовать эту функциональность в коде, потребуется добавить всего
одну строку:
$(".comment-input button").on("click", function (event) {
var $new_comment;
if ($(".comment-input input").val() !== "") {
var $new_comment = $("<p>").text($(".comment-input input").val());
$(".comments").append($new_comment);
$(".comment-input input").val("");
}
});

(QWHU
Еще одна вещь, которой обязательно будут ожидать пользователи, — отправка
комментария при нажатии клавиши Enter на клавиатуре. Это привычное положение
вещей, например, при работе с мессенджерами.
Как мы можем этого добиться? Нужно добавить дополнительный обработчик
событий, который улавливает нажатие клавиши непосредственно в элементе вво-
да. Мы можем добавить его сразу после обработчика нажатия:
$(".comment-input input").on("keypress", function (event) {
console.log("hello, world!");
});

Обратите внимание на два важных различия между этим и предыдущим обработ-


чиками. Первое заключается в том, что новый обработчик настроен на улавливание
нажатия клавиши клавиатуры, а не щелчка кнопкой мыши. Второе — в том, что мы
перехватываем события в другом элементе: в данном случае внимание направлено
на само поле ввода, тогда как в предыдущем обрабатывали элемент button.
Если вы успели испробовать этот код, то увидели, что надпись «Hello, World!»
появляется в логе консоли разработчика Chrome каждый раз, когда мы нажимаем
какую-то клавишу. Но нам нужно, чтобы нажатие большинства клавиш было про-
игнорировано и реакция наблюдалась только тогда, когда пользователь нажимает
клавишу Enter. Чтобы добиться этого, мы можем использовать локальную перемен-
ную, которая не понадобилась нам в предыдущем разработчике, — она будет хранить
значение с информацией о нажатой клавише. Как можно увидеть это значение?
Немного поменяем код:
$(".comment-input input").on("keypress", function (event) {
console.log("Это значение keyCode " + event.keyCode);
});
Глава 4. Интерактивность

Обратите внимание на то, что в слове keyCode буква С прописная. Это пример
camelCase — «верблюжьего регистра» («горбатый Регистр», «стиль Верблюда»):
когда мы даем переменной имя, объединяющее несколько слов, каждое из них,
кроме первого, нужно писать с большой буквы.
Для вывода в лог мы используем знак «+» для связывания значения keyCode со
строкой, которая начинается с «Это значение keyCode». Запустив код, увидим
действительное значение keyCode, выведенное в лог.
Сейчас, открыв браузер и начав что-то набирать в поле ввода, мы увидим зна-
чения keyCode, выводимые на экран одно за другим. Воспользовавшись этим, мы
узнаем значение keyCode для клавиши Enter. Сделав это, мы можем переработать
код в условии if так, чтобы он реагировал только на нажатие клавиши Enter:
$(".comment-input input").on("keypress", function (event) {
if (keyCode === 13) {
console.log("Это значение " + event.keyCode);
}
});

Теперь код выводит значение keyCode только при нажатии клавиши Enter. И на-
конец, надо скопировать код из другого обработчика событий, который добавляет
новый комментарий:
$(".comment-input input").on("keypress", function (event) {
var $new_comment;
if (event.keyCode === 13) {
if ($(".comment-input input").val() !== "") {
var $new_comment = $("<p>").text($(".comment-input input").val());
$(".comments").append($new_comment);
$(".comment-input input").val("");
}
}
});

’’
Ну вот, сейчас все важнейшие функциональности должны работать. Но добавим
еще один аспект взаимодействия: пусть комментарий отображается на странице
постепенно вместо простого немедленного появления. К счастью, с помощью jQuery
сделать это очень просто, так как в каждый элемент jQuery встроен метод fadeIn.
Но для того, чтобы элемент плавно отобразился, нужно, чтобы изначально он был
скрыт. Поэтому перед добавлением элемента в DOM мы должны применить к нему
метод hide. Это сделает следующий код:
$(".comment-input button").on("click", function (event) {
var $new_comment;
if ($(".comment-input input").val() !== "") {
$new_comment = $("<p>").text($(".comment-input input").val());
$new_comment.hide();
$(".comments").append($new_comment);
Первое интерактивное приложение

$new_comment.fadeIn();
$(".comment-input input").val("");
}
});

А сейчас добавим новый комментарий с помощью кнопки. Вы увидите, что он


отображается на странице постепенно вместо немедленного появления. Таким
же образом нужно модифицировать и события, наступающие после нажатия
клавиши Enter, но перед тем, как заняться этим, отправьте новый коммит в хра-
нилище.

’!’
Сейчас мой (и ваш тоже) файл app.js должен выглядеть так:
var main = function () {
"use strict";
$(".comment-input button").on("click", function (event) {
var $new_comment;
if ($(".comment-input input").val() !== "") {
$new_comment = $("<p>").text($(".comment-input input").val());
$new_comment.hide();
$(".comments").append($new_comment);
$new_comment.fadeIn();
$(".comment-input input").val("");
}
});
$(".comment-input input").on("keypress", function (event) {
var $new_comment;
if (event.keyCode === 13) {
if ($(".comment-input input").val() !== "") {
$new_comment = $("<p>").text($(".comment-input input").val());
$new_comment.hide();
$(".comments").append($new_comment);
$new_comment.fadeIn();
$(".comment-input input").val("");
}
}
});
};
$(document).ready(main);

Вы, наверное, сразу заметили, что код дублируется. В частности, код, добавля-
ющий комментарий, повторяется в обоих обработчиках событий, а когда мы меня-
ем один из них, чтобы заставить текст появляться постепенно, то вынуждены таким
же образом изменить и другой. Это противоречит одному из принципов разработ-
ки, известному как DRY, что значит Don’t Repeat Yourself («Не повторяйся»).
Когда вы копируете и вставляете код, в вашем сознании должен прозвучать тре-
вожный звоночек: подумайте, нет ли лучшего пути, чтобы достичь нужного резуль-
тата? В данном случае можем переписать код вот так:
Глава 4. Интерактивность

var main = function () {


"use strict";
var addCommentFromInputBox = function () {
var $new_comment;
if ($(".comment-input input").val() !== "") {
$new_comment = $("<p>").text($(".comment-input input").val());
$new_comment.hide();
$(".comments").append($new_comment);
$new_comment.fadeIn();
$(".comment-input input").val("");
}
};
$(".comment-input button").on("click", function (event) {
addCommentFromInputBox();
});
$(".comment-input input").on("keypress", function (event) {
if (event.keyCode === 13) {
addCommentFromInputBox();
}
});
};
$(document).ready(main);

В этом примере мы делаем дублирующийся код абстрактной функцией, а затем


вызываем ее в каждом обработчике событий. Мы можем добиться этого с помощью
объявления переменной для хранения функций и затем определения функции. Таким
образом, код становится гораздо более удобным в обслуживании: решив внести изме-
нения в механизм комментирования, мы должны проделать это только в одном месте!

!’M4XHU\
Каким насыщенным был этот пример, но, надеюсь, вы все-таки с ним справились!
С его помощью мы немного познакомились с множеством возможностей jQuery.
А сейчас сделаем шаг назад и поговорим о том, с чем мы сейчас работали. Вообще
jQuery предоставляет нам три основные возможности:
 упрощенный и четко организованный способ манипулирования элементами DOM;
 способ последовательной обработки событий DOM;
 упрощенный подход к AJAX.
Изучим две первые из них в этой главе, а третью — в следующей. Нужно отме-
тить также, что в jQuery есть огромное количество сторонних плагинов, с помощью
которых можно быстро и просто улучшить любой сайт.

ø
Перед тем как приступить, уделим немного времени обсуждению рабочего процес-
са. В целом файлы клиентской стороны приложения будут делиться на HTML, CSS
Общие сведения о jQuery

и JavaScript. Таким образом, всегда будет полезно каким-то образом их организо-


вать.
Начиная с этого момента будем хранить файлы HTML в корневом каталоге
проекта; кроме того, там будут находиться три подкаталога: stylesheets (для таблиц
стилей), images (для изображений), javascripts (для сценариев). Таким образом, мы
всегда будем знать, где находятся нужные файлы.
В соответствии с этим придется немного изменить привязку к таблицам стилей
или загрузку сценариев. Вот пример того, как такие изменения повлияют на HTML:
<!doctype html>
<html>
<head>
<link href="stylesheets/reset.css" rel="stylesheet">
<link href="stylesheets/style.css" rel="stylesheet">
</head>
<body>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="javascripts/app.js"></script>
</body>
</html>

Обратите внимание: мы ясно указываем, где размещаются сценарии и таблицы


стилей, с помощью слэша и наименования папки.

Прежде чем начать работать над реальным кодом, поговорим о комментариях


в JavaScript. Как и в HTML или CSS, мы можем добавлять комментарии в код
JavaScript для удобства его понимания человеком. Комментарии могут быть двух
типов: однострочные и многострочные. Однострочные комментарии создаются
с помощью двух слэшей, следующих один за другим, а многострочные выглядят
точно так же, как комментарии CSS:
// это однострочный комментарий, продолжающийся до конца строки
var a = 5; // это однострочный комментарий, располагается на одной строке с кодом
/* А это многострочный комментарий, который будет продолжаться,
пока мы не закроем его. Он выглядит точно так же, как комментарий в CSS. */

ø#
Как мы уже убедились в предыдущем примере, селекторы в jQuery очень похожи
на селекторы CSS. Фактически можно использовать любой селектор CSS в каче­стве
селектора для jQuery. Например, вот эти селекторы jQuery будут действовать точ-
но так, как мы ожидаем:
$("*"); // выбираются все элементы в документе
$("h1"); // выбираются все элементы h1
Глава 4. Интерактивность

$("p"); // выбираются все элементы р


$("p .first"); // выбираются все абзацы класса 'first'
$(".first"); // выбираются все элементы класса 'first'
$("p:nth-child(3)"); // выбираются все абзацы, являющиеся дочерними элементами
третьего уровня

Но на самом деле селекторы jQuery идентичны селекторам CSS не всегда.


В jQuery существует богатый набор псевдоклассов и псевдоэлементов, которые
в CSS недоступны. Кроме того, некоторые верные для CSS идентификаторы долж-
ны представляться в jQuery иначе (специальные символы, например «.», должны
отделяться двумя обратными слэшами). Но для решения наших задач вполне до-
пустимо принять, что селекторы jQuery — то же самое, что селекторы CSS, которые
указывают на элементы DOM для последующего управления ими с помощью
JavaScript. Если вы хотите изучить эту тему подробнее, можете обратиться к доку-
ментации по селекторам в jQuery.

ß%’20
После того как мы с помощью селекторов успешно добавили элементы в DOM,
можно начать управление ими. В jQuery это очень просто.

%’20
Для начала очень полезно запомнить древовидную ментальную модель DOM.
Например, рассмотрим следующий фрагмент HTML:
<body>
<h1>Это пример!h1>
<main>
</main>
<footer>
</footer>
</body>

Обратите внимание на то, что элементы main и footer пусты. Мы можем нари-
совать для такого примера диаграмму, похожую на показанную на рис. 4.3.

Рис. 4.3. Древовидная диаграмма элементов DOM перед переходом к управлению jQuery
Общие сведения о jQuery

Чтобы создать элемент, мы должны использовать тот же самый оператор $, что


и для выбора элемента. Но вместо селектора указываем в нем тег, представляющий
элемент, который нужно создать:
// Как договорились, я даю переменным объектов jQuery названия, начинающиеся с $
var $newUL = $("<ul>"); // создание нового элемента списка
var $newParagraphElement = $("<p>"); // создание нового абзаца

После того как мы создали элемент, можем добавлять туда разные объекты,
включая другие HTML-элементы. Например, можем добавить текст в элемент
абзаца вот так:
$newParagraphElement.text("Это абзац");

После исполнения кода абзац в DOM будет эвивалентен следующему коду


HTML:
<p>Это абзац</p>

Новые элементы ul и p еще не являются частью DOM, которая отображается на


странице, поэтому они будут выведены из древовидной диаграммы, но необходимо
учесть их с помощью соответствующих переменных jQuery (рис. 4.4).

Рис. 4.4. Элементы ul и p уже созданы, но еще не добавлены в DOM

Мы можем добавить их на страницу, выбрав элемент, который будет для них


родительским, а затем вызвав функцию append для объекта jQuery. Например,
если мы хотим сделать элемент дочерним для footer, следует написать вот такую
строку:
$("footer").append($newParagraphElement);
Глава 4. Интерактивность

Сейчас новый элемент будет находиться в подвале и, следовательно, присоеди-


няется к древовидной диаграмме! Это показано на рис. 4.5.

Рис. 4.5. DOM после того, как элемент p был добавлен в подвал

На самом деле мы можем создавать куда более сложные структуры DOM до


того, как они отобразятся на странице. Например, можем добавить элементы li
в элемент ul, созданный ранее:
// мы можем соединить наше творение и обратиться к нему, чтобы добавить текст
var $listItemOne = $("<li>").text("Это первый элемент списка");
var $listItemTwo = $("<li>").text("Второй элемент списка");
var $listItemThree = $("<li>").text("Боже, третий элемент списка");
// а сейчас мы привяжем эти элементы к ul, созданному ранее
$newUL.append($listItemOne);
$newUL.append($listItemTwo);
$newUL.append($listItemThree);
В этой точке у нас получилось поддерево, не связанное с остальной частью де-
рева (рис. 4.6).
А сейчас предположим, что мы хотим присоединить это поддерево к основной
части в качестве потомка элемента main. Это делается аналогично показанному
выше, но связывать нужно только корень поддерева!
$("main").append($newUL);
Сейчас наша DOM выглядит так, как показано на рис. 4.7.
Как только у нас появляется выбранный элемент DOM, работа с jQuery допус-
кает некоторую гибкость. Например, мы можем поставить элементы на более вы-
сокий уровень вместо более низкого, что сделает их первым дочерним элементом
родительского узла:
Общие сведения о jQuery

Рис. 4.6. DOM, содержащая поддерево с элементом ul, не связанное с остальной частью

Рис. 4.7. DOM, куда входит связанное поддерево, представляющее собой маркированный список

var $footerFirstChild = $("<p>").text("Я первый дочерний элемент подвала!");


$("footer").prepend($footerFirstChild);
Глава 4. Интерактивность

В этом массиве пять элементов, каждый из которых является текстовой строкой.


Вот пример массива, где все элементы — целые числа:
var primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];

Мы можем индексировать этот массив и взаимодействовать с отдельными эле-


ментами с помощью имени переменной и квадратных скобок. Нумерация элемен-
тов массива всегда начинается с 0 и заканчивается числом на 1 меньшим, чем
длина массива:
console.log(greetings[1]);
//=> 'привет'
console.log(greetings[0]);
//=> 'здравствуйте'
console.log(primes[4]);
//=> 11
console.log(greetings[4]);
//=> 'алоха'

Аналогичным образом присваиваются значения отдельным элементам массива:


greetings[3] = "приветствую"; // меняет "салют" на "приветствую"

А что, если нужно набрать все элементы массива? Один из способов сделать
это — использовать свойства массива length для создания условия продолжения
цикла for:
var index;
for (index = 0; index < greetings.length; index = index + 1) {
console.log(greetings[index]);
}

Это очень хороший способ, и он часто используется как в JavaScript, так и в дру-
гих языках, например Java. Однако в JavaScript есть лучший, джаваскриптовый,
способ достичь того же результата. С каждым массивом связан цикл forEach, кото-
рый заставляет функцию работать с каждым элементом массива:
// Цикл forEach loop принимает функцию в качестве аргумента
// и запускает ее для каждого элемента массива
greetings.forEach(function (element) {
console.log(element);
});

Это гораздо лучше, так как позволяет избежать использования лишних пере-
менных, например index, как в примере ранее. А ведь нередко бывает, что удаление
объявления переменных снижает количество ошибок в нашем коде.
В дополнение к включению функции forEach массивы JavaScript имеют еще
несколько преимуществ над массивами в C++ или Java. Во-первых, они могут
расти или уменьшаться динамически. Во-вторых, у них есть несколько встроенных
функций, что позволяет быстро выполнять какие-то общие операции. Например,
часто встречается необходимость добавить элементы в конец массива. Для этого
можно использовать функцию push:
Использование JSLint для выявления возможных проблем

// создаем пустой массив


var cardSuits = [];
cardSuits.push("трефы");
console.log(cardSuits);
//=> ["трефы"]
cardSuits.push("бубны");
console.log(cardSuits);
//=> ["трефы", "бубны"]
cardSuits.push("червы");
console.log(cardSuits);
//=> ["трефы", "бубны", "червы"]
cardSuits.push("пики");
console.log(cardSuits);
//=> ["трефы", "бубны", "червы", "пики"]
В массивах JavaScript очень много других встроенных функций, но пока мы
остановимся на push. Несколько других изучим в дальнейшем.
Массивы и циклы — очень нужные инструменты независимо от того, с каким
языком программирования вы работаете. Единственный способ хорошо изучить
их — это практиковаться в их создании и использовании. Несколько практических
задач в конце главы позволят вам поупражняться в создании массивов и управле-
нии ими с помощью циклов.

$-6/LQW’#’’
#
Как и в случае HTML и CSS, очень просто написать код JavaScript, который рабо-
тает, но не соответствует принятым стандартам качества. Особенно легко это
удается, когда вы только начинаете работу с языком. Например, посмотрите на
следующий код:
cardRank = "король";
if (cardRank = "король") {
console.log("карта — король!");
} else {
console.log("карта — не король!");
}
//=> карта — король!
На первый взгляд код выглядит хорошо и вроде бы должен работать. Наберите
его в Google Chrome в консоли JavaScript — и вы увидите тот результат, который
ожидался. Но немного изменим код — пусть значение cardRank будет «дама»:
cardRank = "дама";
if (cardRank = "король") {
console.log("карта — король!");
} else {
console.log("карта — не король!");
}
//=> карта — король!
Глава 4. Интерактивность

Теперь-то вы точно заметили ошибку. Дело в том, что условие if содержит


оператор назначения вместо сравнения, в результате чего переменной всегда на-
значается псевдоправдивое значение1. Пока не обязательно понимать, что именно
означает псевдоправдивое (truthy) и почему оно заставляет выполняться блок кода,
но надо разобраться, из-за чего происходит ошибка: случайно было использовано
= (назначение) вместо === (сравнение).
Исправим ее, поменяв назначение на сравнение:
if (cardRank === "король") {
console.log("карта — король!");
} else {
console.log("карта — не король!");
}

Но в этом коде есть еще одна скрытая проблема. К счастью для нас, существует
аналог CSS Lint для JavaScript, называемый вполне логично JSLint. Домашняя
страница JSLint показана на рис. 4.9.

Рис. 4.9. Домашняя страница JSLint

1
Автор использует жаргонные слова truthy и falsy для обозначения ситуации, когда
значение переменной заранее преобразуется в логическое «истинно» или «ложно»
соответственно. Емкий перевод обнаружился на Habrahabr и звучит как «значения
правдочки и кривдочки». — Примеч. пер.
Использование JSLint для выявления возможных проблем

Вставим наш код в JSLint и посмотрим, что нам скажут. Сервис должен ответить
так, как показано на рис. 4.10.

Рис. 4.10. Первые ошибки в JSLint

Первые ошибки, которые мы видим, относятся к скрытой ошибке, которую


я упоминал ранее. JavaScript не требует обязательного объявления переменных
перед использованием, но это часто может привести к непредсказуемым послед­
ствиям. Лучше все же объявить все переменные с помощью ключевого слова var.
Изменим код так, чтобы он объявлял переменную в дополнение к ее определе-
нию:
var cardRank = "король";

Сделав это и перезапустив проверку, мы увидим, что первые две ошибки исчез-
ли. Следующая — ошибка назначения, о которой мы уже говорили. Исправив ее,
перейдем к двум последним.
Последние две ошибки связаны с глобальной переменной console, которая была
использована до своего объявления. Мы не вправе просто добавить слово var перед
этой переменной, поскольку не создаем ее. Но можно перейти к опциям JSLint,
включить глобальную опцию console, alert... после чего эти ошибки исчезнут.
Как и CSS Lint, JSLint эмулирует присутствие рядом с вами опытного настав-
ника, профессионала в JavaScript, который проверяет, как вы пишете код. Порой
его замечания будут не совсем понятными и вам придется какое-то время разби-
раться, в чем состоит ошибка и как ее исправить (отличная стартовая точка для
этой работы — Google), но все же оно того стоит, и постепенно вы научитесь писать
код JavaScript более эффективно.
Глава 4. Интерактивность

$PD]HULI¿F
А сейчас рассмотрим все наши идеи на примере. Работа с приложением Amazeriffic
началась с определения цели — приложение должно выполнять функции списка
задач. Для ее достижения построим интерфейс, состоящий из трех вкладок. Первая
будет отображать список задач, начиная с самых новых (рис. 4.11).

Рис. 4.11. Первая вкладка, где задачи отсортированы от новых к старым

Вторая вкладка будет отображать тот же самый список, но начиная с самых


старых задач (рис. 4.12).

Рис. 4.12. Вторая вкладка, где задачи отсортированы от старых к новым


Добавление интерактивности Amazeriffic

А на последней вкладке будет находиться поле ввода, с помощью которого


можно добавлять новые задачи (рис. 4.13).

Рис. 4.13. Вкладка позволяет добавить новую задачу

Поскольку мы уже много работали с Git к этому моменту, в этом примере я предо-
ставляю вам возможность самостоятельно выбирать подходящие моменты для
обновления хранилища. Просто убедитесь, что вы создали хранилище Git и регу-
лярно обновляете его по мере рабоы с этим примером.
Вы также увидите, что большая часть дизайна повторяет пример из предыдущей
главы. Поэтому вы можете просто скопировать свою работу (а именно HTML и CSS)
из предыдущей главы. Только в этом случае нужно создать каталоги scripts и stylesheets
для лучшей организации работы.

ø$
Вы можете начать с изменения элемента main, который содержит наш интерфейс.
Мой HTML-код для этого элемента выглядит вот так:
<main>
<div class="container">
<div class="tabs">
<a href=""><span class="active">Новые</span></a>
<a href=""><span>Старые</span></a>
<a href=""><span>Добавить</span></a>
Глава 4. Интерактивность

</div>
<div class="content">
<ul>
<li>Купить продукты</li>
<li>Обновить несколько новых задач</li>
<li>Подготовиться к лекции в понедельник</li>
<li>Ответить на письма нанимателей в LinkedIn</li>
<li>Вывести Грейси на прогулку в парк</li>
<li>Закончить писать книгу</li>
</ul>
</div>
</div>
</main>
Вы можете использовать данную базовую структуру элемента main, а также свои
наработки по стилю из главы 3. Вот как выглядят стилевые правила для моих
вкладок:
.tabs a span {
display: inline-block;
border-radius: 5px 5px 0 0;
width: 100px;
margin-right: 10px;
text-align: center;
background: #ddd;
padding: 5px;
}
Вы видите здесь один новый элемент: я использую свойство display со значени-
ем inline-block. Напомню, что внутристрочные элементы наподобие span не имеют
свойства ширины (width), в отличие от блочных, но последние всегда начинаются
с новой строки. Свойство inline-block создает гибридный элемент, давая элементу
span свойство width, для которого я установил значение 100 пикселов. Таким обра-
зом, все вкладки у нас будут одной ширины.
Конечно, у меня есть дополнительный набор правил для активной вкладки.
С его помощью цвет верхней части активной вкладки делается таким же, как у ее
содержимого, в результате чего создается иллюзия объема интерфейса:
.tabs a span.active {
background: #eee;
}

$
А сейчас перейдем к действительно интересной части — JavaScript! Конечно, к это-
му моменту jQuery и /javascripts/app.js через элемент script уже должны быть
включены в нижнюю часть элемента body в нашем HTML. Поэтому можем начать
работу над файлом app.js с создания основной структуры программы:
var main = function () {
"use strict";
Добавление интерактивности Amazeriffic

console.log("hello, world!");
};
$(document).ready(main);

Напомню, что если все было сделано правильно, то, открыв ее в окне браузера,
мы увидим hello, world! в консоли JavaScript. Проверьте это.

ł
Если все работает правильно, займемся переключением вкладок. Начнем с кода,
который обрабатывает щелчки кнопкой мыши на каждой вкладке:
var main = function () {
"use strict";
$(".tabs a:nth-child(1)").on("click", function () {
// делаем все вкладки неактивными
$(".tabs span").removeClass("active");
// делаем активной первую вкладку
$(".tabs a:nth-child(1) span").addClass("active");
// очищаем основное содержание, чтобы переопределить его
$("main .content").empty();
// возвращается false, так как мы не переходим по ссылке
return false;
});
$(".tabs a:nth-child(2)").on("click", function () {
$(".tabs span").removeClass("active");
$(".tabs a:nth-child(2) span").addClass("active");
$("main .content").empty();
return false;
});
$(".tabs a:nth-child(3)").on("click", function () {
$(".tabs span").removeClass("active");
$(".tabs a:nth-child(2) span").addClass("active");
$("main .content").empty();
return false;
});
};

Внимательно изучив код, вы заметите, что мы нарушили принцип DRY («Не


повторяйся») — определенно здесь присутствует некоторое дублирование. Придет-
ся перевести какое-то количество кода в абстракции с помощью функции. Попро-
буйте сделать это прямо сейчас, не заглядывая вперед. Продолжайте. Я подожду.

!$&
Надеюсь, что какое-то время вы размышляли над решением, прежде чем прочесть
его! Пришло ли вам в голову применить функцию, где в качестве аргумента ис-
пользуется номер вкладки? Если да — отлично! Если нет, все хорошо — скоро вы
с этим разберетесь. Нужно всего лишь немного практики.
Это нормально, если ваше решение отличается от моего. Самое главное — по­
старайтесь разобраться в принципе работы этого решения:
Глава 4. Интерактивность

var main = function () {


"use strict";
var makeTabActive = function (tabNumber) {
// сконструируем селектор из tabNubmer
var tabSelector = ".tabs a:nth-child(" + tabNumber + ") span";
$(".tabs span").removeClass("active");
$(tabSelector).addClass("active");
};
$(".tabs a:nth-child(1)").on("click", function () {
makeTabActive(1);
return false;
});
$(".tabs a:nth-child(2)").on("click", function () {
makeTabActive(2);
return false;
});
$(".tabs a:nth-child(3)").on("click", function () {
makeTabActive(3);
return false;
});
};

Возможно, вы попробовали поместить return false внутрь функции makeTabActive,


но это может привести к проблемам. Нужно оставить этот оператор внутри обра-
ботчика щелчков, так как обработчик должен вернуть false, иначе браузер будет
пытаться перейти по ссылке.

!$&
Итак, мы немного сократили количество строк в коде и, более того, сделали это
способом, позволяющим избежать ошибок. Но можно пойти еще дальше. Обрати-
те внимание на то, как мы пометили вкладки: с помощью цифр 1, 2, 3. Если мы
преобразуем их в цикл for, который будет перебирать все эти номера, то сможем
настроить слушатели совсем просто! Попробуйте сделать это.
Таким образом мы даже избавимся от необходимости использования функции
makeTabActive, так как поместим важные строки кода непосредственно в цикл. Мое
решение выглядит вот так:
var main = function () {
"use strict";
var tabNumber;
for (tabNumber = 1; tabNumber <= 3; tabNumber++) {
var tabSelector = ".tabs a:nth-child(" + tabNumber + ") span";
$(tabSelector).on("click", function () {
$(".tabs span").removeClass("active");
$(this).addClass("active");
return false;
});
}
};
Добавление интерактивности Amazeriffic

! $&
IRU(DFK
Оказывается, есть еще одно решение! jQuery позволяет создать набор элементов,
а затем перебрать их через массив. В этом упрощении мы переберем все элементы
span внутри вкладок, создав обработчик щелчков (click) для каждой:
var main = function () {
"use strict";
$(".tabs a span").toArray().forEach(function (element) {
// создаем обработчик щелчков для этого элемента
$(element).on("click", function () {
$(".tabs a span").removeClass("active");
$(element).addClass("active");
$("main .content").empty();
return false;
});
});
};

Массив, который создает jQuery, является массивом элементов DOM, а не объектов


jQuery. Нам нужно преобразовать их в объекты jQuery, поместив внутрь функциональ-
ного вызова $().

Как видите, есть три способа решения подобных задач. Могут быть, конечно,
и другие, но, думаю, два последних неплохи — они коротки, понятны (если, конеч-
но, вы умеете работать с циклами), а кроме того, с их помощью легко добавлять
дополнительные вкладки.

ß#
Теперь нужно решить другую проблему. Внутри вкладок элемент main .content
должен заполняться по-разному в зависимости от того, какая вкладка выбрана.
Это очень легко, если мы знаем, на каком потомке элемента .tabs находимся. На-
пример, если активен первый потомок элемента .tabs, мы делаем одно, если вто-
рой — что-то другое. Но на самом деле это не так-то просто, потому что внутри
обработчика событий мы имеем доступ только к элементу span, который является
потомком интересующего нас элемента. Древовидная диаграмма этой ситуации
изображена на рис. 4.14.
Оказывается, что в jQuery существует отличный способ выбрать предок объ-
екта jQuery — функция, которая так и называется — parent. Но затем нужно опре-
делить, с которым по счету потомком мы имеем дело. Для этого в jQuery есть
функция is, которая позволяет проверить селектор относительно текущего объ-
екта jQuery. Наверное, понять эту абстракцию нелегко, но на практике все это
вполне читаемо. Вот короткий пример:
Глава 4. Интерактивность

Рис. 4.14. Нам нужно знать индекс элемента span, на котором мы щелкнули

// проверим, является ли предок объекта jQuery $me


// первым потомком своего собственного предка
if ($me.parent().is(":first-child")) {
console.log("МОЙ ПРЕДОК — ПЕРВЫЙ ПОТОМОК!!");
} else {
console.log("Мой предок не является первым потомком.");
}
Мы можем использовать этот паттерн и селектор :nth-child, чтобы определить
необходимые действия для примера с вкладками:
var main = function () {
"use strict";
$(".tabs a span").toArray().forEach(function (element) {
// создаем обработку щелчков для этого элемента
$(element).on("click", function () {
// поскольку мы используем версию элемента jQuery,
// нужно создать временную переменную,
// чтобы избежать постоянного обновления
var $element = $(element);
$(".tabs a span").removeClass("active");
$element.addClass("active");
$("main .content").empty();
if ($element.parent().is(":nth-child(1)")) {
console.log("Щелчок на первой вкладке!");
} else if ($element.parent().is(":nth-child(2)")) {
console.log("Щелчок на второй вкладке!");
} else if ($element.parent().is(":nth-child(3)")) {
console.log("Щелчок на третьей вкладке!");
}
return false;
});
});
};
Добавление интерактивности Amazeriffic

Запустив этот код с открытой панелью JavaScript в Chrome, вы увидите соот-


ветствующее сообщение, появляющееся при щелчке на определенной вкладке!
Супер-пупер просто!

ı
Настройка содержимого вкладки требует несколько более сложной работы. Прежде
всего нужно создать сами списки задач в виде массива строковых элементов.
Для этого добавим переменную внутрь функции main, где будет храниться массив
текстовых констант с задачами:
var main = function () {
"use strict";
var toDos = [
"Закончить писать эту книгу",
"Вывести Грейси на прогулку в парк",
"Ответить на электронные письма",
"Подготовиться к лекции в понедельник",
"Обновить несколько новых задач",
"Купить продукты"
];
//... все остальное, относящееся к вкладкам
};

Теперь, когда появятся новые задачи, все, что необходимо сделать, — добавить
их в конец массива. Таким образом получается, что старые задачи стоят в масси-
ве первыми, а новые — последними. Вторая вкладка (Старые) будет перечислять
задачи в том же порядке, что и в массиве, а первая вкладка (Новые) — в обрат-
ном.
Для начала я покажу, как будет перестраиваться контент в случае щелчка на
второй вкладке, а затем предоставлю вам возможность создать содержимое осталь-
ных двух. Содержимое первой и второй вкладок создать очень просто: нужно
добавить элемент ul, а затем с помощью циклической работы с массивом добавить
элементы li для каждого элемента массива. Обратите внимание на то, что для
первой вкладки нужно будет работать с элементами в обратном порядке, поэтому
в данном случае мы используем традиционный цикл for. Для второй же можем
воспользоваться более удобным циклом forEach. Код может выглядеть примерно
так:
$(element).on("click", function () {
var $element = $(element),
$content;
$(".tabs a span").removeClass("active");
$element.addClass("active");
$("main .content").empty();
if ($element.parent().is(":nth-child(1)")) {
console.log("Щелчок на первой вкладке!");
} else if ($element.parent().is(":nth-child(2)")) {
$content = $("<ul>");
Глава 4. Интерактивность

toDos.forEach(function (todo) {
$content.append($("<li>").text(todo));
});
$("main .content").append($content);
} else if ($element.parent().is(":nth-child(3)")) {
console.log("Щелчок на третьей вкладке!");
}
})
Как я уже упоминал, построение содержимого для первой вкладки выглядит
аналогичным образом. После того как мы это сделаем, содержание, жестко пропи-
санное в HTML, больше не нужно! Мы можем отправить (trigger) фальшивый
щелчок на первой вкладке, добавив одну строку в конце функции main, сразу после
обработчиков щелчков. Это динамически сконструирует содержимое:
$(".tabs a:first-child span").trigger("click");
Если все работает правильно, можно удалить жестко прописанные в index.html
элементы.
Для третьей вкладки нужно проделать кое-что другое — создать элементы input
и button, такие же, с которыми мы работали в примере с комментариями в начале
главы. Но в данном случае нужно поупражняться в создании поддерева DOM с по-
мощью jQuery вместо внедрения их в HTML. Следует также добавить обработчик
событий для кнопки. В данном случае вместо добавления элемента в DOM, как мы
делали в более раннем примере, нужно просто поместить его в массив ToDo с помо-
щью функции Push.
Завершенный пример вы можете видеть в хранилище Git.

В этой главе мы изучили, как сделать веб-приложение интерактивным с помощью


jQuery и JavaScript. jQuery — широко используемая библиотека, которая берет на
себя некоторые трудности управления DOM и обработки событий, а также многое
другое. Из-за того что запущенный в браузере пользовательский интерфейс явля-
ется событийно-управляемым, появляется необходимость создания асинхронного
кода. Самый простой способ начать — это прикрепление функций обратных вызо-
вов к событиям.
JavaScript является полнофункциональным языком программирования, под­
держиваемым всеми браузерами. Для начинающего веб-разработчика и програм-
миста очень важно приобрести опыт в работе с несколькими фундаментальными
концепциями, включающими переменные, условия if и if-else, а также разнооб-
разные циклические структуры. Массивы — еще один необходимый элемент во всех
языках программирования, поэтому научиться создавать их и управлять ими так-
же очень важно.
JSLint — сервис, аналогичный CSS Lint или HTML Validator, — поможет вам
избежать самых распространенных ошибок в JavaScript. Очень полезно время от
времени прогонять через него свой код.
Больше теории и практики

Ø$  

Добавим несколько шагов к упражнениям на запоминание.


1. Создайте папку под названием javascripts для хранения файлов .js.
2. Создайте простую программу JS и сохраните ее в файле с названием app.js.
Файл должен или выдавать предупреждение «hello, world!», или вводить его
в консоль. Это должно быть организовано в функции main, которая должна
вызываться функцией jQuery document.ready.
3. Импортируйте сценарий вместе с jQuery в свой документ HTML в нижней
части тега <body>.
4. Заново откройте страницу в Chrome и убедитесь, что все работает хорошо.
5. Добавьте файл и изменения в HTML в хранилище Git.

#M4XHU\
Одна из причин популярности jQuery — существование крупного сообщества веб-
разработчиков, которые создают плагины, позволяющие добавлять на вашу страни-
цу невероятные эффекты. Я рекомендую посетить страницу http://plugins.jque­ry.com
и посмотреть, какие плагины там предлагают. Кроме того, советую поэксперимен-
тировать с некоторыми плагинами. Придется почитать документацию по плагинам,
чтобы научиться работать с ними, но, поверьте, это отличное вложение вашего
времени.
Один из моих любимых плагинов называется colorbox. Он позволяет легко до-
бавить на свою страницу анимированную фотогалерею. Автор написал очень про-
стую документацию по работе с этим плагином.

ø#M4XHU\
Наберите следующий код HTML и сохраните его в файле selectorpractice.html:
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Привет</h1>
<h2 class="important">Снова привет</h2>
<p>Случайные несвязанные абзацы</p>
<div class="relevant"> //relevant — релевантный (см.ниже)
<p class="a">first</p>
<p class="a">second</p>
Глава 4. Интерактивность

<p>third</p>
<p>fourth</p>
<p class="a">fifth</p>
<p class="a">sixth</p>
<p>seventh</p>
</div>
</body>
</html>

Затем создайте простой файл app.js с нижеследующим содержимым. Исполь-


зуйте два тега <script>, чтобы импортировать jQuery и вновь созданный файл в ниж-
нюю часть элемента body:
var main = function () {
"use strict;"
$("*").css("color","red");
};
$(document).ready(main);

Функция css позволяет менять стиль выбранных элементов с помощью jQuery.


По умолчанию код использует универсальный селектор CSS и меняет цвет каждо-
го элемента DOM на красный. Изменим его так, чтобы красными становились
только определенные элементы. Например, если мы хотим сделать красным толь-
ко тег <h1>, нужно использовать следующий селектор:
$("h1").css("color","red");

Следующие несколько шагов будут куда проще, если вы просмотрите докумен-


тацию jQuery по селекторам. Кроме селекторов, с которыми вы уже знакомы из
этой главы, обратите особое внимание на селекторы :not и :gt.
1. Выберите элементы h2 по их классу.
2. Выберите первый абзац из релевантных абзацев.
3. Выберите третий абзац из релевантных абзацев.
4. Выберите все релевантные абзацы на странице.
5. Выберите все релевантные абзацы.
6. Выберите второй, четвертый и шестой релевантные абзацы.
7. Выберите седьмой релевантный абзац.
8. Выберите пятый, шестой и седьмой релевантные абзацы.
9. Выберите все релевантные абзацы, у которых нет класса а.

)L]]%X]]
Если вы подумываете о том, чтобы работать программистом, вам обязательно нуж-
но уметь решать задачку FizzBuzz. Насколько я знаю, эта задача стала популярной
после поста в блоге Джеффа Этвуда под названием «Почему программисты не
могут… программировать?» (Why Can’t Programmers… Program?). В посте Джефф
Больше теории и практики

указывает на тот факт, что многие люди, работающие программистами, не могут


решить эту простую задачу.
Вот как выглядит эта задача: напишите программу, которая выводит числа от
1 до 100. Но вместо тех, что делятся на 3, пусть будет выведено Fizz, а вместо тех,
что делятся на 5, — Buzz. Вместо тех же, что делятся и на 3, и на 5, пусть будет
выведено FizzBuzz.
Вы вполне можете решить эту задачку с помощью цикла for, а также других
инструментов, которым я научил вас в этой главе. Используйте серии условий if-
else и оператор остатка от деления.
Я должен добавить, что не считаю эту задачу особенно хорошим тестом на спо-
собность кого-либо программировать, и не думаю, что ненайденное решение может
быть поводом для увольнения программиста из-за несоответствия занимаемой
должности. Существует множество причин, по которым кто-либо может не спра-
виться с этой задачей, не имеющих никакого отношения к написанию компьютер-
ных программ на уровне начинающего. Но все-таки что есть, то есть, и, возможно,
когда-нибудь вам зададут этот вопрос. Поэтому постарайтесь найти решение и за-
помнить его.

ß’
Еще один часто встречающийся источник вопросов на собеседованиях — массивы.
Это легко объяснимо: определенно это самая часто встречающаяся структура дан-
ных в компьютерных программах, в той или иной форме они присутствуют в каж-
дом языке программирования. Попробуйте ответить на все вопросы в этом разделе
с помощью материала данной главы. Начнем с простого вопроса с несколькими
вариантами ответа: напишите функцию, которая принимает массив чисел в качес-
тве аргумента и возвращает их сумму.
Самое простое решение будет выглядеть вот так:
var sum = function (nums) {
var sumSoFar = 0,
i;
// цикл перебирает все элементы массива, по очереди прибавляя их к сумме
for (i = 0; i < nums.length; i++) {
sumSoFar = sumSoFar + nums[i];
}
// Сейчас, после перебора всего массива,
// в переменной sumSoFar должна находиться
// сумма всех элементов
return sumSoFar;
};
sum([1,2,3,4]);
//=> 10
Аналогично мы можем использовать цикл forEach:
var sum = function (nums) {
var sumSoFar = 0;
Глава 4. Интерактивность

// use a forEach loop


nums.forEach(function (value) {
sumSoFar = sumSoFar + value;
});
return sumSoFar;
};
sum([1,2,3,4]);
//=> 10

Цикл forEach более предпочтителен, так как с ним нет необходимости в исполь-
зовании переменной i. В общем случае это неплохо — удаление переменной, регу-
лярно изменяющей свое состояние в программе, снижает вероятность появления
в нашем коде ошибок. В самом деле, если мы хотим отделаться от всех временных
локальных переменных, то можем использовать функцию reduce, которая выполнит
то же самое более красиво:
var sum = function (nums) {
return nums.reduce(function (sumSoFar, value) {
return sumSoFar + value;
}, 0);
};
sum([1,2,3,4]);
//=> 10

Я не собираюсь больше уделять время функции reduce, но если она вас заинте-
ресовала, то вы можете почитать о ней подробнее в Интернете.
Следует также отметить, что мы никак не проверяем корректность вводимых
данных для нашей функции. Например, что, если мы попробуем отправить нечто,
не являющееся массивом?
sum(5);
//=> TypeError!
sum("hello, world");
//=> TypeError!

Существует множество способов это исправить, но пока не стоит тратить на это


время. Но, если вы пишете код, например, на собеседовании, будет очень неплохо
проверять вводные данные для каждой функции.
Вот еще несколько вопросов, которые помогут вам поупражняться в работе с мас-
сивами.
1. Напишите функцию, которая принимает в качестве аргумента массив чисел,
а затем возвращает их среднее.
2. Напишите функцию, которая принимает в качестве аргумента массив чисел,
а затем возвращает наибольшее число в массиве.
3. Напишите функцию, которая принимает массив чисел, а затем возвращает true,
если он содержит хотя бы одно четное число, и false — в ином случае.
4. Напишите функцию, которая принимает массив чисел, а затем возвращает true,
если каждое число в массиве четное, и false — в противном случае.
Больше теории и практики

5. Напишите функцию, которая принимает два аргумента: массив строк и отдель­


ную строку — и возвращает true, если строка уже содержится в массиве, и false —
в противном случае. Например, функция может работать вот так:
arrayContains(["привет", "мир"], "привет");
//=> true
arrayContains(["привет", "мир"], "пока");
//=> false
arrayContains(["привет", "мир", "пока"], "пока");
//=> true

6. Напишите функцию, которая похожа на предыдущую, но возвращает true толь-


ко в случае, если массив содержит данную строку по крайней мере дважды:
arrayContainsTwo(["a","b","a","c"], "a");
//=> true
arrayContainsTwo(["a","b","a","c"], "b");
//=> false
arrayContainsTwo(["a","b","a","c","a"], "a");
//=> true

После того как вам удалось это сделать, напишите функцию под названием
arrayContainsThree, которая работает аналогичным образом, но для трех вместо двух.
Словом, мы можем обобщить задачу. Напишите функцию, которая принимает три
аргумента и возвращает true, если массив содержит указанный элемент n раз, где
n — третий аргумент:
arrayContainsNTimes(["a","b","a","c","a"], "a", 3);
//=> true
arrayContainsNTimes(["a","b","a","c","a"], "a", 2);
//=> true
arrayContainsNTimes(["a","b","a","c","a"], "a", 4);
//=> false
arrayContainsNTimes(["a","b","a","c","a"], "b", 2);
//=> false
arrayContainsNTimes(["a","b","a","c","a"], "b", 1);
//=> true
arrayContainsNTimes(["a","b","a","c","a"], "d", 0);
//=> true

3URMHFW(XOHU
Еще один отличный источник практических задач — проект Эйлера (Project Euler).
Там находится множество практических задач, для корректного решения которых
не требуется глубоких знаний. Большинство из них сводятся к нахождению опре-
деленного числа. Получив решение, вы вводите его на сайте и, если ответ правиль-
ный, получаете доступ к обсуждениям, где другие люди публикуют свои решения
и ищут лучшие способы.
Однажды на собеседовании в известной IT-компании мне задали вопрос, кото-
рый почти слово в слово повторял задачу из проекта Эйлера.
Глава 4. Интерактивность

#-DYD6FULSW
JavaScript — популярный язык, и поэтому о нем написано множество книг. К со-
жалению, Дуг Крокфорд клеймит большинство из них как «совершенно кошмар-
ные. Они содержат ошибки, плохие примеры и прививают неправильные навыки».
Я склонен согласиться с ним (и надеюсь, что он не поместит в эту категорию и мою
книгу!).
Как я уже говорил, мне не удалось найти хорошую книгу, которая должным
образом освещала бы программирование на JavaScript для новичков или тех, кто
только приступает к изучению программирования. В то же время существует не-
сколько великолепных книг для программистов среднего и продвинутого уровня.
Я очень рекомендую книгу Крокфорда JavaScript: The Good Parts (O’Reilly, 2008),
как одну из лучших по программированию на JavaScript в целом. Я также думаю,
что в книге Дэвида Германа Effective JavaScript (Addison-Wesley, 2012) содержит-
ся множество хороших практических советов для программистов на JavaScript
среднего уровня.
Если же вы ищете общую практическую информацию по разработке ПО с по-
мощью JavaScript, я очень рекомендую вам книгу Ника Закаса Maintanable JavaScript
(O’Reilly, 2012). И как только вы освоитесь с объектно-ориентированным програм-
мированием на JavaScript, обратитесь к книге Майкла Фогаса Functional JavaScript
(O’Reilly, 2013), которая предлагает иные, но очень увлекательные перспективы.
Мы уже почти закончили увлекательное путешествие по клиентской стороне
веб-приложения. Хоть я не уточнял этого, когда мы обсуждали клиентскую сто-
рону, но я говорю сейчас о той части приложения, которая запускается в браузе-
ре. Другая сторона медали — серверная часть приложения, которая запускает
и хранит информацию, находящуюся вне вашего браузера, обычно на удаленном
компьютере.
Эта глава не о серверной стороне нашего веб-приложения, а о многообразии
технологий, которые позволяют клиенту и серверу обмениваться информацией
самым простым путем. Я обычно представляю себе этот набор технологий как мост
между клиентом и сервером.
Говоря конкретнее, мы с вами изучим объекты JavaScript, JSON (JavaScript
Object Notation) и AJAX (Asynchronous JavaScript And XML — не совсем верное
название). Изучение этих тем подготовит нас к работе с Node.js, который мы будем
изучать в следующей главе.

"#-DYD6FULSW
Прежде чем начать разговор о передаче данных между компьютерами, нужно об-
судить один важный примитив JavaScript — объекты. Вы, может быть, слышали
ранее об объектно-ориентированном программировании, и если вам приходилось
программировать на С++ или Java, то вы хорошо знакомы с этой темой.
Хотя эти принципы очень важны для разработки программного обеспечения
в целом, объектно-ориентированное программирование на JavaScript реализовано
несколько иначе, так что лучше всего забыть о них, начиная изучать объекты. Пока
нам стоит рассматривать их несколько упрощенно, полагая, что объекты — это
просто коллекции переменных, относящихся к одной конкретной сущности. Начнем
с примера.

#
В предыдущей главе мы рассмотрели несколько примеров с игрой в карты. Играль-
ная карта имеет два основных свойства: масть (пики, трефы, бубны или червы)
Глава 5. Мост

и номинал (цифровые от 2 до 10, а также старшие карты — валет, дама, король и туз).
Углубимся в этот пример и создадим веб-приложение для игры в покер. Это по­
требует от нас представления пяти карт в руке с помощью JavaScript.
Самый простой подход с использованием инструментов, с которыми я вас по­
знакомил, потребует использования десяти переменных, по одной для каждого
номинала и по одной для каждой масти:
var cardOneSuit = "червей",
cardOneRank = "двойка",
cardTwoSuit = "пик",
cardTwoRank = "туз",
cardThreeSuit = "пик",
cardThreeRank = "пятерка",
// ...
cardFiveSuitRank = "семерка";
Надеюсь, вы понимаете, что это очень громоздкое решение. И если как следует
изучили предыдущую главу, то, наверное, думаете, что массив из пяти элементов
мог бы все упростить! Но проблема в том, что у каждой карты по два атрибута. И что
же нам делать с этим массивом?
Можно предположить решение, которое выглядит так:
var cardHandSuits = ["червей", "пик", "пик", "треф", "бубен"],
cardHandRanks = ["двойка", "туз", "пятерка", "король", "семерка"];
Определенно уже лучше, но основная проблема предыдущего решения осталась:
нет конкретной связи между отдельным номиналом и отдельной мастью — мы
должны удерживать все связи в голове. Всякий раз, когда в программе есть какие-
либо сущности, тесно связанные между собой, но эти связи существуют только в со-
знании программиста, надо сделать вывод, что решение неоптимально. А на самом
деле это и не обязательно, если мы будем использовать объекты!
Объект — это просто собрание разнообразных переменных, каким-то образом
связанных между собой. Для его создания нужно использовать фигурные скобки,
после чего можно получить доступ к внутренним переменным объекта с помощью
оператора «точка» (.). Вот пример создания отдельной карты:
// создадим объект "карта" номиналом 2 (two)
// червовой масти (hearts)
var cardOne = { "rank":"двойка", "suit":"червей" };
// Выведем номинал cardOne
console.log(cardOne.rank);
//=> двойка
//Выведем масть cardOne
console.log(cardOne.suit);
//=> червей
Создав объект, мы всегда можем изменить его в дальнейшем. Например, изме-
нить масть suit и достоинство rank для cardOne:
// изменим карту на туз пик
cardOne.rank = "туз";
Привет, объекты JavaScript!

cardOne.suit = "пик";
console.log(cardOne);
//=> Object {rank: "туз", suit: "пик"}

Как и в случае с массивом, можем создать пустой объект, а позже добавить ему
атрибуты:
// создаем пустой объект
var card = {};
// присвоим ему номинал туза
card.rank = "туз";
console.log(card);
//=> Object {rank: "туз"}
// присвоим масть червей
card.suit = "червей";
console.log(card);
//=> Object {rank: "туз", suit: "червей"}

Сейчас, если нам требуется представить раздачу карт, мы можем создать массив
и заполнить его карточными объектами вместо создания двух отдельных массивов!
// создаем пустой массив
var cards = [];
// отправляем в массив двойку червей
cards.push( {"rank": "двойка", "suit":"червей"} );
cards.push( {"rank": "туз", "suit":"пик"} );
cards.push( {"rank": "пятерка", "suit":"пик"} );
cards.push( {"rank": "король", "suit":"треф"} );
cards.push( {"rank": "семерка", "suit":"бубен"} );
// открываем первую и третью карты в раздаче
console.log(cards[0]);
//=> Object {rank: "двойка", suit: "червей"}
console.log(cards[2]);
//=> Object {rank: "пятерка", suit: "пик"}

Можно создать и один большой массив, в буквальном смысле представляющий


раздачу карт:
// создаем раздачу карт
// с помощью большого массива
var cards = [
{"rank": "двойка", "suit":"червей"},
{"rank": "туз", "suit":"пик"},
{"rank": "пятерка", "suit":"пик"},
{"rank": "король", "suit":"треф"},
{"rank": "семерка", "suit":"бубен"}
]

Как я упоминал ранее, мы можем думать об объектах JavaScript как о собрании


переменных, каждая из которых обладает именем и значением. Чтобы создать
Глава 5. Мост

пустой объект, просто вводим две фигурные скобки — открывающую и закрыва­


ющую:
// создаем пустой объект
var s = {};

Затем можем добавить переменные к объекту с помощью оператора «точ­ка» (.):


s.name = "Сэмми";

Переменные внутри объекта могут быть любого типа, включая строки, ко-
торые мы видели во всех предыдущих примерах, а также массивы или даже
объекты:
s.age = 36; // число, означающее возраст
s.friends = [ "Марк", "Эмили", "Брюс", "Сильван" ]; // друзья
s.dog = { "name":"Грейси", "breed":"Австралийская овчарка" };
/ собака по имени Грейси породы австралийская овчарка*/
console.log(s.age);
//=> 36
console.log(s.friends[1]);
//=> "Эмили"
console.log(s.dog);
//=> Object {name: "Грейси", breed: " Австралийская овчарка"}
console.log(s.dog.name);
//=> "Грейси"

Мы также можем создавать литералы, которые являются, по сути, просто объ-


ектами, полностью определенными в коде:
var g = {
"name": "Гордон",
"age": 36,
"friends": ["Сара", "Энди", "Роджер", "Брендон"],
"dog": {"name":"Пи", "breed":"Метис лабрадора" }
}
console.log(g.name);
//=> "Гордон"
console.log(g.friends[2]);
//=> "Роджер"
console.log(g.dog.breed);
//=> "Метис лабрадора"

При необходимости мы можем использовать специальную величину null, что


значит «нет объекта»:
var b = {
"name": "Джон",
"age" : 45,
"friends" : [ "Сара", "Джим" ],
"dog" : null
}
Обмен информацией между компьютерами

В этом примере у Джона нет собаки. Мы можем использовать null также для
того, чтобы обозначить окончание работы с этим объектом:
// назначаем currentPerson объекту g
var currentPerson = g;
// ... выполняем какие-то действия с currentPerson
// устанавливаем currentPerson на null
currentPerson = null;

Будем использовать величину null в качестве заполнителя для объекта, особен-


но часто — в главе 6, когда будем говорить об ошибках. В частности, функции об-
ратного вызова будут активизироваться величиной null в том случае, когда запрос
не содержит ошибок.
Вообще говоря, объекты предоставляют нам множество возможностей для хра-
нения данных и управления ими. Надо также отметить, что мы можем хранить
в объектах переменные, так как функциональные переменные в JavaScript работа-
ют так же, как и любые другие. Например, мы уже знаем, как можно получить доступ
к функциям, которые связаны с объектами jQuery:
// получаем элемент DOM
var $headerTag = $("h1");
// $headerTag — объект, у которого есть связанная с ним функция,
// называемая fadeOut
$headerTag.fadeOut();

Объекты со связанными функциями — очень мощный инструмент для абстрак-


ций (что и привело к идее объектно-ориентированного программирования), но мы,
пожалуй, отложим их обсуждение на некоторое время. Вместо этого в данной гла-
ве мы сконцентрируемся на использовании объектов для обмена информацией
с другими веб-приложениями.

$&
В наши дни почти невозможно создать веб-приложение, не затрагивая других уже
существующих приложений. Например, вы хотите реализовать возможность авто-
ризации ваших пользователей через аккаунт Twitter. Или ваши пользователи
должны получать обновления от вашего приложения в свою ленту в Facebook. Это
значит, что у вашего приложения должна быть возможность обмена информацией
с этими сервисами. Стандартный формат, используемый в веб-приложениях се-
годня, называется JSON, и, если вы хорошо освоили работу с объектами JavaScript,
значит, уже можете работать с JSON!

-621
Объект JSON — это не что иное, как объект JavaScript в виде строки (с некоторыми
техническими нюансами, но большинство из них мы пока что можем игнорировать).
Это значит, что, если нам нужно отправить какую-либо информацию на другой
Глава 5. Мост

сервис, мы просто создаем в коде объект JavaScript, преобразуем его в строку и от-
правляем. В большинстве случаев библиотеки AJAX сделают за нас основную ра-
боту, и для программиста это выглядит так, будто программы просто обменивают-
ся объектами!
Например, предположим, что я хочу определить объект JSON как внешний
файл — я могу просто кодировать его, словно внутри программы JavaScript:
{
"rank":"десятка",
"suit":"червей"
}
Оказывается, что в большинстве языков программирования очень просто взять
строку JSON и сконвертировать ее в объект, который может использовать компью-
терная программа! А в JavaScript это и вовсе не представляет никаких сложностей,
так как сам по себе этот формат — просто строковая версия литерала объекта.
Большинство окружений JavaScript поддерживают объекты JSON, с которыми мы
можем взаимодействовать. Например, откройте консоль Google Chrome и введите
следующее:
// создаем строку JSON в единичных кавычках
var jsonString = '{"rank":"десятка", "suit":"червей"}'
// JSON.parse преобразует ее в объект
var card = JSON.parse(jsonString);
console.log(card.rank);
//=> "ten" десятка
console.log(card.suit);
//=> "червей"

Обратите внимание на то, что мы создали строку jsonString с использованием одинарных


кавычек вместо двойных. Дело в том, что для JavaScript не имеет значения, какие имен-
но вы используете, но для JSON это важно. Поскольку нужно создать строку внутри
строки, мы используем одинарные кавычки снаружи и двойные внутри.

Можно также преобразовать объект JSON в строку с помощью функции


stringify:
console.log(JSON.stringify(card));
//=> {"rank":"ten","suit":"червей"}
А сейчас, когда мы выяснили, как создавать объекты JSON во внешних файлах
или в виде строк в наших программах, посмотрим, как можно обмениваться ими
между компьютерами.

$-$;
AJAX расшифровывается как Asynchronous JavaScript And XML (асинхронный
JavaScript и XML, что, как я уже говорил, не совсем верно). Общий формат обмена
Обмен информацией между компьютерами

данными, предшествовавший JSON, назывался XML и больше напоминал HTML.


Но несмотря на то, что XML по-прежнему широко используется, продвижение
к JSON — очень серьезный шаг с момента изобретения AJAX.
Несмотря на то что аббревиатура полна технических терминов, на самом деле она
вовсе не сложна. Основная идея AJAX — возможность для приложения получать
информацию и отдавать ее другим компьютерам без перезагрузки веб-страницы.
Один из первых примеров этой технологии (и пока что один из лучших) — прило-
жение Google для электронной почты, Gmail, появившееся на сцене около 10 лет
назад! Если вы пользуетесь им, то, наверное, замечали, как новое письмо магическим
образом появляется в ваших входящих. Другими словами, вам не нужно переза­
гружать страницу, чтобы получить свежую почту. Это и есть пример AJAX.

-621
Ну, пожалуй, хватит теории. Посмотрим на работу AJAX в действии. Начнем с со-
здания структуры приложения. На этом этапе обойдемся без CSS и просто выпол-
ним пример для работы. Надеюсь, к этому моменту вы уже способны создать по
памяти страницу, которая приветствует нас фразой «Hello, World!» c помощью
элемента h1 и имеет пустой элемент main. Нам также нужно включить элемент script
со ссылкой на jQuery из CDN (мы говорили об этом в главе 4), а также тег <script>,
содержащий ссылку на файл app.js — основное приложение JavaScript. Этот файл
должен находиться в папке javascripts и выглядеть примерно так:
var main = function () {
"use strict";
console.log("Hello, World!");
}
$(document).ready(main);

Запустите базовое приложение в Chrome и откройте консоль JavaScript. Как


обычно, если все идет хорошо, вы увидите в логе слова «Hello, World!».
Затем нужно создать новую папку внутри каталога с нашим примером и назвать
ее cards. Внутри нее мы создадим файл aceOfSpades.json, который будет выглядеть
как одно из наших предыдущих определений объектов JavaScript:
{
"rank" : "туз",
"suit" : "пик"
}

Сейчас мы хотим получить доступ к этому файлу из нашей программы с помо-


щью AJAX, что в данный момент составляет определенную проблему.


На момент появления JavaScript был предназначен для работы в браузере. Это озна-
чало, что по соображениям безопасности доступ к локальным файлам, хранящимся
Глава 5. Мост

на вашем компьютере, запрещается. Можете себе представить проблемы, которых


нельзя было бы избежать в противном случае, — любой сайт, посещенный вами,
получал бы полный доступ к вашему компьютеру!
Само собой разумеется, что это ограничение по безопасности актуально и до
сих пор. В то же время у нас есть право доступа к определенным типам файлов
с того же самого сервера, откуда был получен сам файл JavaScript. Файлы JSON
являются отличным примером: я могу сделать любой запрос AJAX на сервер и по-
лучить любые файлы JSON, которые он делает доступными. К сожалению, мы не
будем работать с сервером до начала чтения следующей главы, так что пока при-
дется обойти эти ограничения с помощью Chrome.

Не следует выходить в Интернет при отключенных настройках безопасности. Убедитесь,


что вы перезапустили браузер перед тем, как заходить на сайты, за исключением со-
зданного вами самостоятельно.

Чтобы запустить Chrome без этого ограничения, следует сначала закрыть его.
Затем, если вы работаете в Mac, откройте командную строку и введите следующую
команду:
hostname $ open -a Google\ Chrome --args --allow-file-access-from-files

Если вы работаете в Windows, то можете сделать то же самое, щелкнув на кноп-


ке Пуск, набрав run в строке поиска, а затем введя в окне запуска следующую ко-
манду:
%userprofile%\AppData\Local\Google\Chrome\Application\chrome.exe
–allow-file-access-from-files

Таким образом, Chrome должен открыться с отключенным ограничением по


безопасности, упомянутым ранее. Как я уже сказал, мы используем данный прием
только для примера в этой главе — в следующей главе будем запускать приложение
с реального сервера, и в отключении ограничения по безопасности необходимости
не будет. А сейчас убедимся, что все сработало.

’JHW-621
Если вам удалось успешно отключить ограничение по безопасности в Chrome
между сайтами, вы легко получите доступ к локальному файлу JSON из нашей
программы. Чтобы сделать это, надо использовать функцию в jQuery, которая
называется getJSON. Как и большинство примеров JavaScript, этот запрос jQuery
будет асинхронным, поэтому нужно добавить обратный вызов:
var main = function () {
"use strict";
// getJSON сразу интерпретирует JSON, поэтому
// нет необходимости вызывать JSON.parse
$.getJSON("cards/aceOfSpades.json", function (card) {
Обмен информацией между компьютерами

// вводим карту в консоль


console.log(card);
});
};
$(document).ready(main);

Если все было сделано верно, то, открыв страницу в Chrome (с отключенными
ограничениями по безопасности), мы увидим, что карта появилась в консоли. Это
значит, что отныне мы можем использовать ее в нашей программе, как любой дру-
гой объект JavaScript:
var main = function () {
"use strict";
console.log("Hello, World!");
$.getJSON("cards/aceOfSpades.json", function (card) {
// создаем элемент для хранения карты
var $cardParagraph = $("<p>");
// создаем
$cardParagraph.text(card.rank + " of " + card.suit);
// связываем абзац с картой с main
$("main").append($cardParagraph);
});
}
$(document).ready(main);

-621
Мы можем создавать в файле и более сложные объекты JSON. Например, объект
может состоять из массива объектов, а не единичного объекта. Чтобы проверить
это на практике, создайте следующий файл и назовите его hand.json:
[
{ "suit" : "пик", "rank" : "туз" },
{ "suit" : "червей", "rank" : "десятка" },
{ "suit" : "пик", "rank" : "пятерка" },
{ "suit" : "треф", "rank" : "тройка" },
{ "suit" : "бубен", "rank" : "тройка" }
]

Добавьте следующий getJSON сразу за предыдущим:


var main = function () {
"use strict";
console.log("Hello, World!");
$.getJSON("cards/aceOfSpades.json", function (card) {
// создаем элемент для хранения карты
var $cardParagraph = $("<p>");
// создаем текст для карты
$cardParagraph.text(card.rank + " of " + card.suit);
// присоединяем абзац с картой к элементу main
$("main").append($cardParagraph);
Глава 5. Мост

});
$.getJSON("cards/hand.json", function (hand) {
var $list = $("<ul>");
// hand — массив, поэтому мы можем применить к нему итерационный процесс
// с помощью цикла forEach
hand.forEach(function (card) {
// создаем элемент списка для хранения карты
// и присоединяем его к списку
var $card = $("<li>");
$card.text(card.rank + " of " + card.suit);
$list.append($card);
});
// присоединяем список к элементу main
$("main").append($list);
});
};
$(document).ready(main);

В этом примере мы повторно используем переменную card в цикле forEach во втором


вызове JSON. Так происходит потому, что переменная выходит из работы в конце пер-
вого вызова к JSON. Мы не будем сейчас углубляться в правила охвата переменных
в JavaScript, но я подумал, что следует объяснить этот момент во избежание недопони-
мания.

После запуска приложения мы получим страницу, выглядящую как показано


на рис. 5.1.

Рис. 5.1. Наше первое приложение AJAX


Получение изображений с Flickr

$"
Я понимаю, что этот пример не слишком впечатляющий. На самом деле вы, навер-
ное, подумали, что было бы гораздо проще определить эти объекты прямо внутри
кода. Зачем же понадобился отдельный файл?
Что ж, представьте, что данные поступают к вам с отдельного компьютера. В этом
случае очень пригодится возможность доступа к ним из нашего кода. Фактически
изначально мы можем даже не знать, как выглядит объект! Например, представьте,
что мы хотим получить самые свежие изображения собак, загруженные на Flickr.

)OLFNU
Сделаем еще один скелет приложения, на этот раз с CSS. Откройте Sublime и пе-
рейдите в вашу папку Chapter5, которая находится в Projects. Создайте каталог под
названием Flickr. Внутри него создайте привычные папки javascripts и stylesheets, а так-
же файлы app.js и style.css. Сделайте так, чтобы app.js выводил «Hello, World!»
в консоль JavaScript. Создайте также файл index.html, который свяжет все файлы
вместе для создания основы веб-приложения.
Вау! Если вы смогли сделать все это по памяти, то вы далеко пойдете! Если же
пару раз заглядывали в книгу, то все равно неплохо, но попробуйте все-таки запом-
нить этапы основной подготовки и поупражняться выполнять их. Чем больше
основной работы вы сможете делать по памяти, тем лучше сможете фокусировать
свой мозг на сложных и интересных задачах.
Вот как выглядит мой HTML-файл:
<!doctype html>
<html>
<head>
<title>Приложение для Flickr</title>
<link rel="stylesheet" href="stylesheets/style.css">
</head>
<body>
<header>
</header>
<main>
<div class="photos">
</div>
</main>
<footer>
</footer>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="javascripts/app.js"></script>
</body>
</html>

А сейчас мы просто обобщим пример из документации jQuery, которая находится


по адресу http://api.jquery.com. Немного изменим его, чтобы он лучше соответствовал
Глава 5. Мост

нашим целям. В этом примере мы используем JavaScript для получения изобра-


жений с Flickr, сервиса Yahoo! для публикации изображений. Но перед тем как
сделать это, откройте браузер и введите в адресную строку следующую ссылку:
http://api.flickr.com/services/feeds/photos_public.gne?tags=dogs&format=json.
Ответ должен появиться прямо в вашем браузере в виде JSON. Мой, например,
выглядит вот так:
jsonFlickrFeed({
"title": "Recent Uploads tagged dogs",
"link": "http://www.flickr.com/photos/tags/dogs/",
"description": "",
"modified": "2013-10-06T19:42:49Z",
"generator": "http://www.flickr.com/",
"items": [
{
"title": "Huck and Moxie ride stroller",
"link": "http://www.flickr.com/photos/animaltourism/10124023233/",
"media": {"m":"http://bit.ly/1bVvkn2"},
"date_taken": "2013-09-20T09:14:25-08:00",
"description": "...description string...",
"published": "2013-10-06T19:45:14Z",
"author": "nobody@flickr.com (animaltourism.com)",
"author_id": "8659451@N03",
"tags": "park dog beagle dogs brooklyn ride stroller prospect hounds"
},
{
"title": "6th Oct Susie: \"You know that thing you\'re eating?\"",
"link": "http://www.flickr.com/photos/cardedfolderol/10123495123/",
"media": {"m":"http://bit.ly/1bVvbQw"},
"date_taken": "2013-10-06T14:47:59-08:00",
"description": "...description string...",
"published": "2013-10-06T19:14:22Z",
"author": "nobody@flickr.com (Cardedfolderol)",
"author_id": "79284220@N08",
"tags": "pets dogs animal mammal canine"
},
{
"title": "6th Oct Susie ready to leave",
"link": "http://www.flickr.com/photos/cardedfolderol/10123488173/",
"media": {"m":"http://bit.ly/1bVvpXJ"},
"date_taken": "2013-10-06T14:49:59-08:00",
"description": "...description string...",
"published": "2013-10-06T19:14:23Z",
"author": "nobody@flickr.com (Cardedfolderol)",
"author_id": "79284220@N08",
"tags": "pets dogs animal mammal canine"
}
]
});
Получение изображений с Flickr

Это выглядит несколько сложнее, чем примеры, виденные вами ранее, но ос-
новная структура и формат остаются теми же самыми. Он начинается с основной
информации о запросе, а затем выводит свойство items, которое представляет собой
массив изображений. Каждый элемент массива содержит другой объект, media,
у которого есть свойство m, включающее в себя ссылку на изображение. В главе 2
мы изучили, как добавить изображение в HTML-документ с помощью тега <img> —
сейчас он нам и пригодится.
Проделаем все необходимое шаг за шагом. Начнем с добавления строки в функ­
цию main, которая объявляет URL как переменную, после чего можем вызвать
функцию jQuery getJSON точно таким же образом, что и раньше:
var main = function () {
"use strict";
// на самом деле это всего одна строка,
// но я разделил ее на несколько
// для улучшения восприятия
var url = "http://api.flickr.com/services/feeds/photos_public.gne?" +
"tags=dogs&format=json&jsoncallback=?";
$.getJSON(url, function (flickrResponse) {
//пока мы просто выводим ответ в консоль
console.log(flickrResponse);
});
};
$(document).ready(main);

Сейчас, если все хорошо, запустив этот код, мы увидим, что выдаваемый Flickr
объект выводится в консоли и можно изучить его с помощью прокрутки вверх-
вниз. Это поможет выявить источник проблем, если таковые появятся в дальней-
шем.
Затем изменим код так, чтобы вместо вывода целого объекта программа выво-
дила каждый URL по отдельности. Другими словами, мы проходим по всем эле-
ментам объекта с помощью цикла forEach:
$.getJSON(url, function (flickrResponse) {
flickrResponse.items.forEach(function (photo) {
console.log(photo.media.m);
});
});

Таким образом, в консоли будет выведена последовательность URL — и вы даже


сможете щелкать на них и видеть изображения! Теперь наконец мы можем по-на-
стоящему отправить их в DOM. Чтобы сделать это, используем функцию jQuery
attr, с которой мы пока не сталкивались. Используем ее, чтобы вручную установить
атрибут src для тега <img>:
$.getJSON(url, function (flickrResponse) {
flickrResponse.items.forEach(function (photo) {
// создаем новый элемент jQuery для помещения в него изображения
var $img = $("<img>");
// помещаем в атрибут URL,
Глава 5. Мост

// хранящийся в ответе Flickr


$img.attr("src", photo.media.m);
// прикрепляем тег img к элементу
// main.photos
$("main .photos").append($img);
});
});

Сейчас, перезагрузив страницу, мы и в самом деле увидим изображения с Flickr!


И если изменим в первоначальном URL тег на какой-нибудь другой (вместо dog —
«собака»), целая страница изменится! И как обычно, мы можем значительно улуч-
шить отображение страницы с помощью эффектов jQuery:
$.getJSON(url, function (flickrResponse) {
flickrResponse.items.forEach(function (photo) {
// создаем новый элемент jQuery для хранения изображений
// но пока скрываем его
var $img = $("<img>").hide();
// устанавливаем атрибут для URL,
// находящегося в ответе
$img.attr("src", photo.media.m);
// прикрепляем тег к функции main
// элемента photos, а затем отображаем его
$("main .photos").append($img);
$img.fadeIn();
});
});

Сейчас при повторном открытии страницы изображения будут появляться


постепенно. В практических задачах в конце главы мы изменим этот код так, чтобы
изображения появлялись на странице по очереди, одно за другим.

$
  $PD]HULI¿F
Сейчас, когда мы научились работать с объектами JavaScript и JSON, может
быть полезно немного попрактиковаться и внедрить некоторые изученные функ-
циональности в приложение Amazeriffic. В этом примере добавим теги для
каждого элемента задачи. Мы можем использовать эти теги для сортировки
списка задач другим, более удобным способом. В дополнение мы можем ини-
циализировать список задач из файла JSON вместо массива, жестко помещен-
ного в код.
Для начала скопируем всю папку Amazeriffic из каталога Chapter4 в Chapter5. Это
даст нам отличную стартовую точку: не придется переписывать весь код.
Затем добавим файл JSON, в котором будет находиться список задач. Мы можем
сохранить файл под названием todos.json в основной папке проекта (в той, где
находится файл index.html):
Добавление теговой функциональности в Amazeriffic

[
{
"description" : "Купить продукты",
"tags" : [ "шопинг", "рутина" ]
},
{
"description" : "Сделать несколько новых задач",
"tags" : [ "писательство", "работа" ]
},
{
"description" : "Подготовиться к лекции в понедельник",
"tags" : [ "работа", "преподавание" ]
},
{
"description" : "Ответить на электронные письма",
"tags" : [ "работа" ]
},
{
"description" : "Вывести Грейси на прогулку в парк",
"tags" : [ "рутина", "питомцы" ]
},
{
"description" : "Закончить писать книгу",
"tags" : [ "писательство", "работа" ]
}
]

Вы видите, что файл JSON содержит массив с задачами и каждая задача имеет свой
массив с текстовыми элементами, которые и являются тегами. Наша цель — добиться
того, чтобы теги работали как вспомогательный способ организации списка задач.
Чтобы достичь этого, нужно добавить немного кода jQuery, который будет читать
файл JSON. Но, поскольку функция main, рассмотренная в предыдущей главе, за-
висит от списка задач, нужно модифицировать ее так, чтобы вызов к getJSON про-
исходил до вызова main. Для этого добавим анонимную функцию в вызов docu­
ment.ready, который будет вызывать getJSON, а затем вызывать main с результатом
общей работы:
var main = function (toDoObjects) {
"use strict";
// как main имеет доступ к списку задач!
};
$(document).ready(function () {
$.getJSON("todos.json", function (toDoObjects) {
// вызов функции main с аргументом в виде объекта toDoObjects
main(toDoObjects);
});
});

Появилась небольшая проблема: код не будет работать, так как мы изменили


структуру объекта со списком задач. Ранее это был массив со строками, содержащими
Глава 5. Мост

описание задачи, а сейчас это массив объектов. Если нам нужно, чтобы код работал
как раньше, можно создать массив старого типа из нового с помощью функции map.

’PDS
Функция map берет массив и делает из него новый, применяя функцию к каждому
элементу. Запустите консоль в Chrome и попробуйте следующее:
// создаем массив из чисел
var nums = [1, 2, 3, 4, 5];
// применим функцию map
// для создания нового массива
var squares = nums.map(function (num) {
return num*num;
});
console.log(squares);
//=> [1, 4, 9, 16, 25]
В этом примере функция, которая возвращает num*num, применяется к каждому
элементу для создания нового массива. Этот пример может показаться экзотичес-
ким, но посмотрите на другой, более интересный:
// создаем массив имен
var names = [ "эмили", "марк", "брюс", "андреа", "пабло" ];
// а сейчас мы создадим массив,
// где первая буква каждого имени прописная
var capitalizedNames = names.map(function (name) {
// получаем первую букву
var firstLetter = name[0];
// возвращаем ее в виде прописной
// в строку, начинающуюся с индекса 1
return firstLetter.toUpperCase() + name.substring(1);
});
console.log(capitalizedNames);
//=> ["Эмили", "Марк", "Брюс", "Андреа", "Пабло"]
Как видите, мы создали массив из имен с первой прописной буквой, даже не
перебирая элементы самого исходного массива!
Сейчас, если вы поняли, как работает функция map, очень легко создать новый
массив из старого:
var main = function (toDoObjectss) {
"use strict";
var toDos = toDoObjects.map(function (toDo) {
// просто возвращаем описание
// этой задачи
return toDo.description;
});
// сейчас весь старый код должен работать в точности как раньше!
// ...
};
$(document).ready(function () {
Добавление теговой функциональности в Amazeriffic

$.getJSON("todos.json", function (toDoObjects) {


// вызываем функцию main с задачами в качестве аргумента
main(toDoObjects);
});
});
А сейчас, когда весь старый код работает, как раньше, мы можем создать вклад-
ку Теги.

œ
Начнем с добавления вкладки Теги в пользовательский интерфейс. Поскольку мы
сделали код (относительно) гибким, это будет не очень сложно. Начнем с открытия
файла index.html и добавления кода для вкладки под названием Теги между вклад-
ками Старые и Добавить:
<div class="tabs">
<a href=""><span class="active">Новые</span></a>
<a href=""><span>Старые</span></a>
<a href=""><span>Теги</span></a>
<a href=""><span>Добавить</span></a>
</div>
С помощью этой дополнительной строки мы добавляем вкладку в пользова-
тельский интерфейс. Теперь (почти) все будет работать так, как нужно. Единствен-
ная проблема состоит в том, что создание вкладок основывается на их расположе-
нии в списке, поэтому, щелкнув на вкладке Теги, мы увидим интерфейс для
вкладки Добавить. Вот это точно не то, что нам нужно, но, к счастью, требует лишь
небольших изменений.
Все, что необходимо сделать, — добавить дополнительный блок else-if в сере-
дину кода, обеспечивающего работу вкладок, и слегка переставить цифры. Когда
я проделал это в моем коде, соответствующий фрагмент стал выглядеть вот так:
} else if ($element.parent().is(":nth-child(3)")) {
// ЭТО КОД ДЛЯ ВКЛАДКИ ТЕГИ
console.log("Щелчок на вкладке Теги");
} else if ($element.parent().is(":nth-child(4)")) {
$input = $("<input>"),
$button = $("<button>").text("+");
$button.on("click", function () {
toDos.push($input.val());
$input.val("");
});
$content = $("<div>").append($input).append($button);
}

ø$$
Сейчас, когда мы знаем, как все устроено, подумаем, к чему нужно стремиться на
этой вкладке. Посмотрите на рис. 5.2.
Глава 5. Мост

Рис. 5.2. Наша цель для вкладки Теги

На этой вкладке все имеющиеся теги должны быть перечислены как заголовки,
а после них добавлены все описания задач, подходящие под эту категорию. Это
значит, что некоторые задачи могут появляться в некоторых местах.
Проблема здесь, однако, еще и в том, что способ сохранения объекта JSON не-
сколько затрудняет задачу. Было бы лучше, если бы toDoObjects хранились в фор-
мате, организованном с помощью тегов:
[
{
"name": "покупки",
"toDos": ["Купить продукты"]
},
{
"name": "рутина",
"toDos": ["Купить продукты", "Вывести Грейси на прогулку в парк"]
},
{
"name": "писательство",
"toDos": ["Сделать несколько новых задач", "Закончить писать книгу"]
},
{
Добавление теговой функциональности в Amazeriffic

"name": "работа",
"toDos": ["Сделать несколько новых задач", "Подготовиться к лекции
в понедельник","Ответить на электронные письма", "Закончить писать книгу"]
},
{
"name": " преподавание",
"toDos": ["Подготовиться к лекции в понедельник"]
},
{
"name": "питомцы",
"toDos": ["Вывести Грейси на прогулку в парк "]
}
]

К счастью, мы без труда можем изменить изначальный объект toDoObjects и при-


вести его к такому виду с помощью серий вызовов функций map и forEach! Но пока
оставим эту трансформацию для следующего раздела и сконцентрируемся на со-
здании пользовательского интерфейса. Пока пропишем этот формат (или его
упрощенную версию) жестко в коде для вкладки Теги как переменную под назва-
нием organizedByTag:
} else if ($element.parent().is(":nth-child(3)")) {
// ЭТО КОД ДЛЯ ВКЛАДКИ ТЕГИ
console.log("щелчок на вкладке Теги");
var organizedByTag = [
{
"name": "покупки",
"toDos": ["Купить продукты "]
},
{
"name": "рутина",
"toDos": ["Купить продукты", "Вывести Грейси на прогулку в парк "]
},
/* и т. д. */
];
}

Сейчас наша цель — выполнить итерации с этим объектом, добавив новую сек-
цию в элемент страницы .content, как мы уже делали. Для этого просто добавим
элемент h3 вместе с ul и li для каждого тега. Элементы ul и li организованы так
же, как в предыдущей главе, поэтому стиль может остаться тем же самым. Я также
добавил стили для элементов h3 в файле style.css, чтобы они выглядели так же,
как в предыдущем примере:
} else if ($element.parent().is(":nth-child(3)")) {
// ЭТО КОД ДЛЯ ВКЛАДКИ ТЕГИ
console.log("щелчок на вкладке Теги");
var organizedByTag = [
/* и т. д.*/
]
organizedByTag.forEach(function (tag) {
Глава 5. Мост

var $tagName = $("<h3>").text(tag.name),


$content = $("<ul>");
tag.toDos.forEach(function (description) {
var $li = $("<li>").text(description);
$content.append($li);
});
$("main .content").append($tagName);
$("main .content").append($content);
});
}

Осталось проделать всего две вещи. Нужно модифицировать вкладку Добавить


так, чтобы вместе с новой задачей добавлялся и список категорий, а также решить,
как преобразовать текущий список задач в список, организованный с помощью
тегов. Начнем с последнего. Таким образом наше приложение будет динамически
обновляться при добавлении новых элементов.

ø##

Когда я сталкиваюсь с проблемой, которую не получается решить в несколько строк


кода, что-то подсказывает мне о необходимости применить абстракцию в виде
функции. В данном случае нужно создать функцию с названием organizeByTags,
которая принимает объект, хранящийся в нашей программе, в таком виде:
[
{
"description" : "Сделать покупки",
"tags" : [ "шопинг", "рутина" ]
},
{
"description" : "Сделать несколько новых задач",
"tags" : [ "писательство", "работа" ]
},
/* и т. д. */
]

и преобразует его в объект, выглядящий примерно так:


[
{
"name": "шоппинг",
"toDos": ["Сделать покупки"]
},
{
"name": "рутина",
"toDos": ["Сделать покупки", "Вывести Грейси на прогулку в парк"]
},
/* и т. д. */
]
Добавление теговой функциональности в Amazeriffic

После того как у нас появилась функция, мы можем легко модифицировать ее


и поместить на место жестко прописанного в коде объекта:
} else if ($element.parent().is(":nth-child(3)")) {
// ЭТО КОД ДЛЯ ВКЛАДКИ ТЕГИ
console.log("щелчок на вкладке Теги");
var organizedByTag = organizeByTag(toDoObjects);
}

ı#
Мы можем проверить результаты нашей работы в консоли Chrome, но постепенно,
по мере разрастания и усложнения функций сделать это будет становиться труднее.
В таких случаях я предпочитаю создать внешнюю программу JavaScript, которая
содержит функцию и тестирует ее, выводя в консоль результат работы. Если я до-
волен тем, что вижу, то преобразую результат в рабочий код.
Чтобы сделать это, лучше всего иметь какой-нибудь неиспользуемый файл
HTML, находящийся где-то вне вашей файловой иерархии. Этот файл понадобит-
ся только для того, чтобы запустить сценарий, в котором мы и будем ставить экс-
перименты, поэтому HTML не нуждается в нашей стандартной «рыбе», а может
содержать лишь следующее:
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="test.js"></script>

Сейчас, сохранив этот файл как index.html и создав файл test.js следующего
содержания:
var toDoObjects = [
{
"description" : "Сделать покупки",
"tags" : [ "шопинг", "рутина" ]
},
{
"description" : "Сделать несколько новых задач",
"tags" : [ "писательство", "работа" ]
},
/* etc */
];
var organizeByTags = function (toDoObjects) {
console.log("organizeByTags вызвана");
};
var main = function () {
"use strict";
var organizeByTags = function () {
console.log("organizeByTags вызвана");
};
organizeByTags(toDoObjects);
};
$(document).ready(main);
Глава 5. Мост

мы должны увидеть в консоли сообщение «organizeByTag вызвана», а также пример


объекта, который настроили.
Я рекомендую вам, после того как вы справитесь с этим, уделить немного вре-
мени самостоятельному решению задачи. Она довольно хитрая, но, увидев решение,
вы, скорее всего, в нем разберетесь. Однако попытка найти его покажет вам, как вы
можете справляться самостоятельно.
Добавлю, что есть несколько способов решения, как часто бывает в компьютер-
ном программировании. Я покажу вам свое решение, но лишь в качестве ориенти-
ра, чтобы вы поняли, насколько оно отличается от вашего.

Мое решение относительно просто объяснить в два этапа. Сначала я создаю массив,
который содержит все возможные теги, с помощью перебора изначальной струк-
туры с помощью forEach. После того как я получу необходимый результат, я ис-
пользую функцию map для связи своего массива с тегами с желаемым объектом
с помощью перебора списка задач и выделения тех, у которых есть такой тег.
Начнем с первого этапа. Единственная новая для вас вещь здесь — функция
indexOf, которая работает с любыми массивами. Мы можем видеть, как это работа-
ет, из консоли Chrome:
var nums = [1, 2, 3, 4, 5];
nums.indexOf(3);
//=> 2
nums.indexOf(1);
//=> 0
nums.indexOf(10);
//=> -1

Упрощенно это можно изложить так: если объект содержится в массиве, функ-
ция возвращает индекс этого объекта (все индексы начинаются с 0). Если же в мас-
сиве его нет, функция возвращает –1. Это работает в том числе для строк:
var msgs = ["hello", "goodbye", "world"];
msgs.indexOf("goodbye");
//=> 1
msgs.indexOf("hello");
//=> 0
msgs.indexOf("HEY!");
//=> -1

Эта функция понадобится нам, чтобы избежать добавления в массив дублика-


тов:
var organizeByTags = function (toDoObjects) {
// создание пустого массива для тегов
var tags = [];
// перебираем все задачи toDos
toDoObjects.forEach(function (toDo) {
// перебираем все теги для каждой задачи
Добавление теговой функциональности в Amazeriffic

toDo.tags.forEach(function (tag) {
// убеждаемся, что этого тега
// еще нет в массиве
if (tags.indexOf(tag) === -1) {
tags.push(tag);
}
});
});
console.log(tags);
};

Запустив этот код, увидим выведенные наименования тегов без дубликатов.


Значит, мы уже на полпути к успеху! Во второй части решения я использую функ­
цию map:
var organizeByTags = function (toDoObjects) {
/* первая часть, приведенная выше */
var tagObjects = tags.map(function (tag) {
// здесь мы находим все задачи,
// содержащие этот тег
var toDosWithTag = [];
toDoObjects.forEach(function (toDo) {
// проверка, что результат
// indexOf is *не* равен -1
if (toDo.tags.indexOf(tag) !== -1) {
toDosWithTag.push(toDo.description);
}
});
// мы связываем каждый тег с объектом, который
// содержит название тега и массив
return { "name": tag, "toDos": toDosWithTag };
});
console.log(tagObjects);
};

Сейчас, когда у нас есть функция и она корректно работает, можем вставить ее
в функцию main кода приложения, и он будет работать! Как я уже говорил, эта
проблема может быть решена различными способами, так что будет здорово ис-
пробовать какие-нибудь еще.

œ$#
#
Итак, мы добились того, чтобы элементы списка задач организовывались и ото­
бражались с помощью тегов, но как мы будем добавлять теги к новым элементам,
которые вводим с помощью вкладки Добавить? Это требует модификации кода,
который отвечает за отображение интерфейса вкладки. Я предлагаю преобразовать
его так, чтобы интерфейс выглядел как на рис. 5.3.
Глава 5. Мост

Рис. 5.3. Цель для вкладки Добавить

Как вы видите, там находятся два поля ввода. Во втором из них пользователь
может ввести список тегов, разделенных запятыми, которые затем ассоциируются
с добавляемым объектом.
Если вы до конца разобрались с примером из предыдущей главы, то ваш код,
вероятно, выглядит примерно так:
} else if ($element.parent().is(":nth-child(4)")) {
var $input = $("<input>"),
$button = $("<span>").text("+");
$button.on("click", function () {
toDos.push($input.val());
$input.val("");
});
$content = $("<div>").append($input).append($button);
}

Чтобы наш пользовательский интерфейс выглядел так, как показано на рис. 5.3,


нужно добавить дополнительное поле ввода и метки. Затем мы модифицируем
обработчик $button для создания массива с тегами и вставки его в объекты.
Единственное, что нам нужно для этого знать, — как разделить строковый объ-
ект. Оказывается, что у всех строк есть встроенная функция, называемая split
(разделить), которая именно это и делает — разделяет единичную строку на массив
строк. Как и в случае с большинством изученных нами функций, ее работу можно
наблюдать внутри консоли Chrome:
var words = "hello,world,goodbye,world";
// это разделяет массив в соответствии с запятыми
Подведем итоги

var arrayOfWords = words.split(",");


console.log(arrayOfWords);
//=> ["hello","world","goodbye","world"]
arrayOfWords[1];
//=> "world"
arrayOfWords[0];
//=> "hello"

Таким образом, пользователь может ввести строку слов, разделенных запятыми,


которые являются тегами и могут быть добавлены в объект в виде массива.
И наконец, нужно переработать создание массива toDos из нового массива
toDoObjects. Для этого я скопировал и вставил код, который мы использовали
в верхней части функции top (нарушив таким образом принцип DRY — поду-
майте, как можно избежать этого), в результате чего мой код стал выглядеть вот
так:
} else if ($element.parent().is(":nth-child(4)")) {
var $input = $("<input>").addClass("description"),
$inputLabel = $("<p>").text("Description: "),
$tagInput = $("<input>").addClass("tags"),
$tagLabel = $("<p>").text("Tags: "),
$button = $("<span>").text("+");
$button.on("click", function () {
var description = $input.val(),
// разделение в соответствии с запятыми
tags = $tagInput.val().split(",");
toDoObjects.push({"description":description, "tags":tags});
// обновление toDos
toDos = toDoObjects.map(function (toDo) {
return toDo.description;
});
$input.val("");
$tagInput.val("");
});
}

В этой главе мы изучили основы трех очень важных тем: объекты JavaScript, JSON
и AJAX. Я представил вам объекты JavaScript как способ хранения связанных
данных в программе в виде единого целого. Объекты создаются с использованием
фигурных скобок, а их свойства добавляются и связываются с ними с помощью
оператора «точка» (.).
Вы можете рассматривать JSON как строковое представление объектов JavaScript,
которые могут быть обработаны на любом языке программирования. JSON исполь-
зуется для передачи данных между компьютерными программами — в нашем случае
между браузером (клиентом) и сервером. Кроме того, многие веб-сервисы (включая
Flickr, Twitter и Facebook) предлагают API — интерфейсы программирования
Глава 5. Мост

приложений, которые позволяют отправлять информацию в эти сервисы (или


запрашивать из них) с помощью формата JSON.
Как правило, если нужно отправить информацию в программу или получать
сведения из нее на серверной стороне, мы используем технологию, которая назы-
вается AJAX. Это позволяет динамически обновлять информацию на веб-страни-
це без необходимости перезагрузки. jQuery предлагает несколько функций AJAX,
которые мы изучим в следующих главах, а в этой мы уже познакомились с функ-
цией getJSON.

Ø$  
ø )OLFNU
В этой задаче вам нужно создать простое приложение, которое позволяет пользо-
вателю ввести параметры поиска, а затем отображает серию изображений из Flickr,
содержащих этот тег. Изображения должны появляться и исчезать по очереди.
Для этого нам понадобится функция setTimeout, с которой мы познакомились
в главе 4. Эта функция позволяет нам запланировать событие, которое должно
произойти спустя определенное время. Допустим, например, что мы хотим выве­сти
сообщение «Нello, World!» через 5 секунд. Можем сделать это следующим обра-
зом:
// вывод "hello, world!" через 5 секунд
setTimeout(function () {
console.log("hello, world!");
}, 5000);

Обратите внимание на то, что аргументы этой функции указаны не так, как мы
привыкли (обычно обратный вызов идет последним), а наоборот. Это особенность,
которую нужно просто запомнить. Обратите внимание также на то, что второй
аргумент представляет собой количество миллисекунд, которое необходимо выждать
перед вызовом функции.
А сейчас представим себе, что нужно просто отобразить и скрыть массив сооб-
щений:
var messages = ["первое сообщение", "второе сообщение", "третье", "четвертое"];

Можем начать с простой основы HTML:


<body>
<div class="message"></div>
</body>

Затем нужно настроить функцию main в файле app.js следующим образом:


var main = function () {
var messages = ["первое сообщение", "второе сообщение", "третье", "четвертое"];
var displayMessage = function (messageIndex) {
// создаем и скрываем элемент DOM
Больше теории и практики

var $message = $("<p>").text(messages[messageIndex]).hide();


// очищаем текущее содержимое
// лучше всего будет выделить текущий параграф
// и постепенно скрыть его
$(".message").empty();
// добавляем сообщение с messageIndex вDOM
$(".message ").append($message);
// постепенное отображение сообщения
$message.fadeIn();
setTimeout(function () {
// через 3 секунды вызываем displayMessage снова со следующим индексом
messageIndex = messageIndex + 1;
displayMessage(messageIndex);
}, 3000);
};
displayMessage(0);
}
$(document).ready(main);
Таким образом, сообщения будут отображаться по порядку каждые 3 секунды.
Здесь есть, однако, небольшая проблема: достигнув конца списка сообщений, про-
грамма начнет выводить слово undefined, так как в массиве больше ничего нет. Мы
можем исправить это, добавив оператор if для проверки того, достигли ли мы
конца массива, и, если это так, сбрасывая индекс на 0.

На самом деле, скорее всего, не нужно добавлять этот эффект на реальные веб-страни-
цы. Это современный эквивалент тега <blink>, который был удален из HTML, так как
ужасно всех раздражал.

А сейчас попробуем все это обобщить. Итак, сначала нужно сделать запрос AJAX
для получения данных об изображениях с Flickr, а затем вместо добавления в DOM
элементов абзаца вставить изображение (img вместо p). Для элемента img исполь-
зуем атрибут src, чтобы указать изображение с Flickr.
После того как все это начало работать, создадим интерфейс, позволяющий
пользователю ввести тег для поиска, а затем генерирующий слайд-шоу из изобра-
жений, соответствующих указанному тегу. Это прекрасное упражнение, которое
займет у вас определенное время, особенно если вы решите добавить простейшие
стили и действительно хорошо скоординировать отображение картинок. Я реко-
мендую вам попробовать!

ß’’
"
Одна из задач, которые я очень люблю давать начинающим программистам, связа-
на с определением покерных раздач. Если вы не знакомы с карточными играми,
основанными на покере, ничего страшного. Все они базируются на комбинациях,
Глава 5. Мост

которые могут появляться в раздаче пяти игральных карт. Комбинации могут быть
такие:
 пара — две карты одного номинала;
 две пары — две карты одного номинала и еще две карты другого;
 тройка — три карты одного номинала;
 стрит — пять карт, идущих по порядку, причем туз может считаться как тузом
(следующим после короля), так и единицей;
 флеш — пять карт одной и той же масти;
 фулл-хауз — две карты одного номинала и три карты другого номинала;
 каре — четыре карты одного номинала;
 стрит-флеш — пять карт одной масти, номиналы которых идут по порядку;
 роял-флеш — стрит-флеш, в которой последовательность начинается с десятки
и заканчивается тузом.
Любая другая раздача, где нет таких комбинаций, называется «нет игры». Об-
ратите внимание на то, что комбинации не эксклюзивны — например, раздача,
содержащая тройку, автоматически содержит и пару, а фулл-хауз содержит две
пары. В этой серии задач мы напишем функции JavaScript, которые проверяют,
есть ли в массиве из пяти карт одно из этих свойств.
Решать эти задачи можно разными способами, часть из которых более эффек-
тивны или требуют меньше кода, чем другие. Но наша цель сейчас — поупражнять-
ся в работе с массивами, объектами и условными операторами. Поэтому выберем
подход, который позволяет создать серию вспомогательных функций, которые мы
можем использовать для определения того, содержит ли набор карт определенный
тип комбинации.
Для начала посмотрим, как может выглядеть раздача:
var hand = [
{ "rank":"двойка", "suit":"пик" },
{ "rank":"четверка", "suit":"червей" },
{ "rank":"двойка", "suit":"треф" },
{ "rank":"король", "suit":"пик" },
{ "rank":"восьмерка", "suit":"бубен" }
];

В нашем примере раздача содержит пару двоек. Было бы неплохо создать при-
меры для всех остальных типов комбинаций, прежде чем двигаться дальше.
Как же определить, соответствует ли раздача критериям комбинации? Нуж-
но свести эту задачу к той, которую мы решали в конце главы 4! Если вы реша-
ли те задачи, то, наверное, помните, что нужно было написать функцию под
названием containsNTimes, принимающую три аргумента: массив, элемент для
поиска и минимальное количество раз повторения этого элемента в массиве.
А сейчас представьте, что мы отправляем этой функции массив карточных но-
миналов:
Больше теории и практики

// тут у нас две двойки


containsNTimes(["двойка","четверка","двойка","король","восьмерка"], "двойка", 2);
//=> true

Это говорит нам, что в массиве с номиналами есть две двойки! Мы можем ис-
пользовать тот же принцип для определения наличия тройки или каре.
// тройки из двоек здесь нет
containsNTimes(["двойка","четверка","двойка","король","восьмерка"], "двойка", 3);
//=> false

Таким образом, мы свели задачу к представлению массива карточных объектов


в виде массива карточных номиналов. Для этого очень легко использовать функцию
map, с которой мы познакомились в этой главе:
// "hand" — массив раздачи, определенный ранее
hand.map(function (card) {
return card.rank;
});
//=> ["двойка","четверка","двойка","король","восьмерка"]

Результат работы вызова функции map мы можем обработать как переменную,


а затем отправить его в другую функцию containsNTimes, чтобы определить, есть ли
там пара двоек:
var handRanks,
result;
handRanks = hand.map(function (card) {
return card.rank;
});
//в результате получаем 'true'
result = containsNTimes(handRanks, "two", 2);

И наконец, мы создаем массив для всех возможных номиналов, а затем исполь-


зуем цикл forEach для определения того, содержит ли раздача пару любых из них:
// это все возможные номиналы
var ranks = ["двойка","тройка","четверка","пятерка","шестерка","семерка","восьмерка",
"девятка","десятка","валет","дама","король","туз"];
var containsPair = function (hand) {
// предположим, что пар нет,
// пока не доказано обратного
var result = false,
handRanks;
// создаем массив карточных номиналов
handRanks = hand.map(function (card) {
return card.rank;
});
// поиск пары любого номинала
ranks.forEach(function (rank) {
if (containsNTimes(handRanks, rank, 2)) {
// мы нашли пару
result = true;
Глава 5. Мост

}
});
// в результате получаем true, если пара найдена,
// и false, если нет
return result;
};

После рассмотрения этого примера попробуйте написать функцию для всех


остальных комбинаций (например, containsThreeOfAKind для проверки наличия
тройки). Для двух пар и фулл-хауза понадобится отслеживать значения номиналов
элементов, которые вы обнаружили. А для флеша нужно будет извлекать из мас-
сива объектов масти, а не номиналы.
Случай стрита несколько сложнее, но я дам вам подсказку: стрит не содержит
пар (что вы можете определить в результате работы функции containsPair, ис-
пользовав оператор !), а разница между низшей и высшей картами составляет 4.
Так что лучше всего будет написать набор вспомогательных функций для нахож-
дения высшего и низшего номинала карт в виде чисел (валет в данном случае
можно считать за 11, даму — за 12, короля — за 13, а туз — за 14). После того как
у вас будут эти функции, вы можете определить, является ли раздача стритом,
подтвердив, что отсутствуют пары, а разница между низшей и высшей картами
составляет 4.
Затем можно создать и остальные функции, используя те, что у вас уже есть.
Например, стрит-флеш автоматически содержит стрит, а роял-флеш является
стрит-флешем, где высшая карта — туз (или низшая — 10).

$3,
Теперь, когда вы убедились, как легко получать данные из Flickr и работать с ними,
может быть интересно получить данные из других API. Оказывается, очень многие
API позволяют получать различные данные точно так же, как Flickr.
Портал Programmable Web содержит целый список API. Для сайта, использу-
ющего функцию jQuery getJSON, необходима поддержка технологии под названием
JSONP. Вы можете получить список API, содержащих JSONP, а также почитать
об этой технологии в «Википедии».
Вам придется почитать документацию для определенных API, чтобы понять,
как форматировать URL запроса, но упражнения с различными API — это отличный
способ попрактиковаться в работе с AJAX.
ø

К этому моменту мы уже изучили основные технологии, связанные с клиентской


частью веб-приложения. А также немного познакомились со взаимодействием
браузера и сервера с помощью JSON и AJAX. А сейчас начнем знакомиться с про-
граммированием на серверной стороне!
Понимание серверной стороны приложения потребует от вас более глубого изу-
чения модели «клиент — сервер», протокола HTTP и Node.js. Замечу, что Node.js —
относительно новая (и превосходная!) технология, которая позволяет создавать
событийно-управляемые серверы с помощью JavaScript.

ı’
Настройка рабочего окружения с поддержкой приложений, управляемых базами
данных, может быть непростой задачей, и описание этой процедуры для Windows,
Mac OS и Linux выходит за рамки книги. Для упрощения процесса я создал набор
сценариев, которые помогут вам приступить к работе с использованием VirtualBox
и Vagrant достаточно быстро.
Vagrant — это инструмент для настройки рабочего окружения на виртуальной
машине. Вы можете думать о виртуальных машинах как об отдельных компьютерах,
работающих полностью в пределах вашего компьютера. Мы поговорим о них по­
дробнее в разделе «Ментальные модели». А сейчас просто запомните, что мы будем
использовать Vagrant и VirtualBox для настройки виртуального рабочего окруже-
ния на серверной стороне. Это окружение будет включать в себя большинство
инструментов, которые понадобятся нам в оставшейся части книги.
В общем-то, часть этого процесса связана в большей степени с удобством: хотя
установка рабочего окружения Node.js на вашей локальной машине совсем не
сложна (и в конце главы я буду рекомендовать вам сделать это самостоятельно),
добавление набора инструментов для сервера, включая MongoDB и Redis, доволь-
но нетривиально и требует много времени и терпения.
Другая причина, почему я так поступаю, — стабильность. Поскольку я сам на-
писал сценарии, я получу несколько больше контроля над установленными версия­
ми Node.js, Redis и MongoDB. Более того, использование Vagrant позволит иметь
одно и то же рабочее окружение независимо от того, работаете ли вы в Windows,
Глава 6. Сервер

Mac OS или Linux. Это минимизирует проблемы, возникающие из-за различий


в версиях операционных систем.
Я также думаю, что рабочее окружение внутри VirtualBox отделяет серверную
часть приложения очень ясно. Это создает отличную педагогическую абстракцию
для всех изучающих основы разработки веб-приложений.

ß9LUWXDO%R[
9DJUDQW
Прежде всего вы должны установить последнюю версию VirtualBox. На момент
написания книги это версия 4.2.18. Вы можете зайти на http://www.virtualbox.org
(рис. 6.1), щелкнуть на ссылке Downloads в левой части страницы и скачать соот-
ветствующую версию для вашей операционной системы. Загрузка, установка и на-
стройка будут зависеть от операционной системы, поэтому внимательно следуйте
инструкциям.

Рис. 6.1. Домашняя страница VirtualBox

Затем нужно установить последнюю версию Vagrant (на момент написания


книги это версия 1.4). Чтобы получить последнюю версию, зайдите на http://www.vagran­
tup.com (рис. 6.2), щелкните на ссылке Downloads в верхнем правом углу и скачайте
последнюю версию для вашей платформы. После загрузки дважды щелкните на
пакете и установите его так же, как и VirtualBox.
Установка Virtual Box и Vagrant

Рис. 6.2. Домашняя страница Vagrant

Если вы работаете в Windows, понадобится перезагрузка компьютера, чтобы


убедиться в том, что путь ко всем файлам настроен корректно. Вы можете проверить
это, открыв командную строку (нажмите кнопку Пуск и введите cmd в строку поис-
ка) и введя команду vagrant –version:
C:\Users\semmy>vagrant --version
Vagrant version 1.4.0

Если все получилось, вы готовы к работе. Если нет, перезагрузите компьютер


и попробуйте ввести команду vagrant снова.

ø$ #
Если все получилось, вы готовы к скачиванию хранилища Git под названием node-
dev-bootsrap с моей страницы GitHub. Это значит, что вам нужно скачать все со-
держимое хранилища Git, которое я создал, на свой компьютер. Если вы работаете
в Windows, это потребует от вас открытия командной строки git-bash. В любом
случае пройдите в свою папку Projects и скопируйте туда хранилище node-dev-
bootstrap с помощью следующей команды:
hostname $ git clone https://github.com/semmypurewal/node-dev-bootstrap Chapter6

Таким образом будет создана папка под названием Chapter6, а хранилище node-dev-
bootstrap — скопировано в нее. Затем зайдите в папку и введите команду vagrant up:
hostname $ cd Chapter6
hostname $ vagrant up
Установка Virtual Box и Vagrant

Запустив PuTTY, введите имя хоста и порт, которые вы получили от Vagrant,


в соответствующие поля ввода Host Name и Port (рис. 6.4). Затем щелкните на Open
(Открыть). PuTTY подключит вас к виртуальному серверу и запросит имя поль-
зователя и пароль. То и другое установлено в Vagrant.

Рис. 6.4. Установка PuTTY для подключения к виртуальной машине

1RGHMV
После того как вы подключились, можете работать в командной строке виртуаль-
ного компьютера точно так же, как в командной строке на локальном. Например,
вы можете вывести на экран содержимое какой-либо папки на виртуальной маши-
не тем же способом, каким пользовались на локальной, — с помощью команды ls:
vagrant $ ls
app postinstall.sh

Пока не обращайте внимания на файл postinstall.sh. С помощью навыков ра-


боты с командной строкой, которые вы получили в предыдущих главах, перейдите
в каталог app на вашей виртуальной машине:
vagrant $ cd app
vagrant $ ls
server.js

Здесь вы должны увидеть файл server.js. Проверим, что все работает правиль-
но, начав с команды node:
vagrant $ node server.js
Server listening on port 3000
Глава 6. Сервер

Вы увидите сообщение Server listening on port 3000 («Сервер слушает на пор­


те 3000») и больше не сможете работать с командной строкой из-за того, что сервер
запущен. Вы можете убедиться в этом, открыв Chrome и набрав localhost:3000
в адресной строке. Если все работает правильно, вы должны увидеть сообщение
«Hello, World!». Оно должно выглядеть так, как показано на рис. 6.5.

Рис. 6.5. Сервер Node по умолчанию, отображаемый в Chrome

Самое главное: вы можете остановить программу из командной строки, нажав


Ctrl+C. Окончив работу, можете покинуть виртуальный сервер, введя exit:
vagrant $ exit
logout
Connection to 127.0.0.1 closed.

Если вы авторизованы на своей виртуальной машине с помощью PuTTY в Windows,


это действие закроет программу. Затем можете вернуться к командной строке,
которую вы использовали, чтобы запустить виртуальную машину. Если же вы
работаете в MacOS, то немедленно вернетесь к командной строке. В любом случае
вы всегда можете остановить сервер, набрав vagrant halt:
hostname $ vagrant halt
[default] Attempting graceful shutdown of VM..1

Остановка виртуальной машины может понадобиться, если вы не работаете с ней


в какой-то момент, но она запущена в фоновом режиме. Виртуальная машина по­
требляет очень много ресурсов компьютера. После остановки можете запустить ее
снова командой vagrant up.

Чтобы полностью удалить виртуальную машину, созданную в Vagrant, со своего компью-


тера, нужно использовать команду vagrant destroy. В результате виртуальная машина
будет создана снова в следующий раз, когда вы введете vagrant up из этой папки. Поэ-
тому пока я рекомендую вам работать с командой vagrant halt.

1
Сообщение: «Мягкое завершение работы виртуальной машины…». — Примеч. пер.
Ментальные модели

$#
В данном разделе обсудим различные способы понимания природы клиентов,
серверов, хостов и гостей.

# #
В области компьютерных сетей о программах обычно говорят либо как о серверных,
либо как о клиентских. Традиционно серверная программа рассматривает какой-либо
ресурс как сеть, в которую могут получить доступ несколько клиентских программ.
Например, кому-либо может понадобиться переместить файлы с удаленного компью-
тера на локальный. FTP-сервер — это программа, которая предоставляет протокол
передачи файлов (File Transfer Protocol), позволяющий пользователям сделать это.
А FTP-клиент — это программа, которая устанавливает связь с FTP-сервером и пе-
редает файлы. Самая общая модель «клиент — сервер» представлена на рис. 6.6.

Рис. 6.6. Модель «клиент — сервер»

Многое из происходящего сейчас в мире компьютерных сетей является второ-


степенным для разработки веб-приложений, но нужно разобраться в некоторых
важных вещах.
Во-первых, тот факт, что (в большинстве случаев) клиент — это браузер, а сер-
вер — удаленная машина, которая опосредованным образом предоставляет ресур-
сы через протокол передачи гипертекста HTTP (Hypertext Transfer Protocol). Хотя
изначально он был разработан для обмена HTML-документами между компьюте-
рами, этот протокол может использоваться также для передачи различных типов
ресурсов с удаленного компьютера (в частности, документы, базы данных или
другие типы представлений). В дальнейшем нам придется немало говорить об
HTTP, а пока просто запомните: это протокол, который мы используем, чтобы с по-
мощью браузера связаться с удаленным компьютером.
Глава 6. Сервер

Наши серверы HTTP будут использоваться для доставки клиентской части


приложения, которую сможет интерпретировать браузер. В частности, все HTML,
CSS и JavaScript, изученные нами к этому моменту, будут доставляться браузеру
через сервер. Клиентская часть программы, запущенная в браузере, будет отвечать
за получение информации с сервера и отправку информации на него, как правило,
с помощью AJAX.

# 
Обычно HTTP-сервер запускается на удаленной машине (позднее в этой книге мы
проделаем это самостоятельно). Из-за этого у разработчиков возникают некоторые
проблемы — если код запускается на удаленном сервере, приходится или редакти-
ровать код на удаленном сервере, а затем перезагружать его, или редактировать код
локально и отправлять его на сервер всякий раз, когда нужно попробовать измене-
ния. Это может быть нерационально.
Мы можем обойти эту проблему, запустив сервер на своей машине локально.
Фактически мы делаем таким образом шаг вперед. Вместо того чтобы локально
запустить программу сервера и все программное обеспечение, мы создаем внутри
своего компьютера виртуальную машину, на которой запускается сервер. Для че-
ловека это выглядит так же, как удаленная машина (в том смысле, что мы подклю-
чаемся к ней через SSH, тем же способом, что и к удаленной машине). Но посколь-
ку машина работает внутри нашего компьютера, можем просто предоставить
локальной машине права доступа к папке с рабочими файлами и таким образом
редактировать их.
Локальный компьютер, на котором запущена виртуальная машина, является
хостом (хозяином). Виртуальная машина является гостем. В оставшейся части
главы они оба будут запущены, и я буду четко разделять их.

#
Эта абстракция может привести к недопониманию, которое будет прояснено позже.
Прежде всего поговорим о приложениях, которые будут запущены во время про-
цесса разработки, и об окнах, с которыми они будут связаны.
 Браузер. Вы будете использовать Chrome для тестирования своего приложения.
 Текстовый редактор. Если вы до сих пор делали все правильно, это, скорее все-
го, будет Sublime. Он будет редактировать файлы, находящиеся в папке с раз-
деленными правами доступа как на хостовой, так и на гостевой машине.
 Git. Командная строка Git должна быть открыта всегда. Используйте Git с ма-
шины-хоста. Если вы работаете в Mac OS или Linux, это будет командное окно,
если же в Windows — командная строка git-bash.
 Vagrant. Скорее всего, вы будете взаимодействовать с Vagrant через то же окно,
которое используете для работы с Git. Это значит, что вы можете ввести vagrant
up или vagrant halt в том же самом месте, где набираете git commit.
Привет, HTTP!

 SSH. Он будет запущен на хостовой машине, но вы будете использовать его для


взаимодействия с гостевой. Если вы работаете в Windows, понадобится PuTTY
или дополнительное окно git-bash в зависимости от того, каким образом vagrant
ssh осуществляет связь. Если же вы работаете в Mac OS, лучше всего будет от-
крыть дополнительное командное окно.
Обычно у меня открыто не менее двух командных окон в дополнение к браузе-
ру и текстовому редактору. Частично это иллюстрирует рис. 6.7, где, правда, не
показано окно текстового редактора.

Рис. 6.7. Запущенные в Windows окна git-bash, PuTTY и Chrome

+773
А сейчас, когда мы немного разобрались в отношениях между виртуальной маши-
ной-гостем и машиной-хостом, попробуем понять сам код. Как я уже упоминал,
главное преимущество запуска кода на виртуальной машине, работающей на нашем
локальном компьютере (в отличие от удаленного запуска), — это доступ к редак-
тированию кода прямо с локального компьютера через любой текстовый редактор
по нашему выбору.
Откройте в Sublime папку app внутри каталога Chapter6. Вы найдете там файл
server.js, который выглядит примерно так:
var http = require("http"),
server;
server = http.createServer(function (req, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Hello, World!");
});
server.listen(3000);
console.log("Server listening on port 3000");
Глава 6. Сервер

Этот код не должен показаться вам совершенно незнакомым — вы обязаны


немедленно заметить некоторые объявления переменных, оператор console.log,
анонимную функцию, объект JSON, а затем шаблон обратного вызова, который мы
использовали при построении элементов UI в предыдущих главах.
Но что же делает этот код? Оказывается, довольно много — он создает сервер
HTTP, который отвечает на запросы браузера! Как я уже говорил, HTTP означает
HyperText Transfer Protocol (протокол передачи гипертекста), и это основная тех-
нология, которая стоит за www (World Wide Web — Всемирная паутина). Вы,
возможно, слышали о программах вроде Apache или Nginx — это настраиваемые
серверные программы HTTP промышленного класса, которые используются для
хостинга больших веб-сайтов. Наш HTTP-сервер гораздо проще: он просто прини-
мает запрос браузера и выдает текстовый ответ «Hello, World!».
Этот код можно понять по аналогии с обработчиком щелчков в jQuery — раз-
ница состоит лишь в том, что обратная связь возникает не в ответ на щелчки кноп-
кой мыши, выполняемые пользователем, а в результате выхода на связь клиента
(в данном случае браузера). Параметр req — это объект, представляющий запрос
HTTP, который приходит на наш сервер с клиента, а res — это параметр, который
представляет ответ HTTP, отправляемый обратно клиенту. Функция req.writeHead
создает заголовок HTTP, который устанавливает атрибуты ответа, а функция res.
end завершает ответ, добавляя Hello, World!.
После создания сервера мы начинаем слушание на порте 3000 (вот почему по­сле
localhost в браузере нужно было набрать 3000). Оператор console.log отображает
сообщение в логе, когда мы запускаем программу.

 ([SUHVV
Уф! Не слишком ли много для маленькой программы? А ведь это еще не все — сер-
вер HTTP требует хороших навыков и знаний для корректного написания кода.
К счастью, нет необходимости заботиться о деталях, так как мы импортировали
код через модуль http Node.js. Модуль — это просто какое-то количество кода,
которое мы можем использовать без необходимости полностью понимать его ра-
боту — нужно знать лишь API, который предоставляет код. Первая строка нашего
кода — оператор require, который импортирует модуль http и хранит его в пере-
менной http.
Этот серверный модуль http интересен, если нам нужен самый примитивный,
базовый HTTP-сервер, который просто принимает запросы клиента и отвечает на
них. Поскольку мы хотим начать с отправки файлов HTML или CSS с сервера, все
становится несколько сложнее. Мы можем создать более сложный сервер поверх
базового, который предоставляет нам Node.js, но, к счастью, решение уже создано
до нас! Модуль Express создает поверх ядра модуля http надстройку, которая об-
рабатывает большинство сложных вещей, о которых нам не нужно заботиться
самостоятельно, в частности, обслуживает статичный HTML, CSS и файлы JavaScript
на клиентской стороне.
Модули и Express

В самом деле, одно из преимуществ программирования на Node — наличие множе­


ства модулей наподобие Express, среди которых можно обнаружить очень полезные.
Модуль http, который мы рассматривали ранее, является частью основного дистрибу-
тива Node, поэтому не нужно делать ничего особенного, чтобы использовать его. Express
не входит в основной дистрибутив, поэтому придется приложить некоторые усилия,
прежде чем мы сможем получить доступ к нему так же легко, как к модулю http.
К счастью, каждый дистрибутив Node поставляется со специальной программой,
называемой Node Package Manager (NPM) — менеджер пакетов Node. Этот инструмент
позволяет легко устанавливать модули, а затем немедленно использовать их в коде.

ß([SUHVV!$&130
Использование NPM очень просто. Начнем с создания новой папки внутри ката-
лога app и назовем ее Express. Вы можете сделать это из Sublime или из командной
строки. В любом случае, как только у вас появится эта папка, свяжитесь с машиной
Vagrant через SSH и перейдите в папку Express. Затем можете набрать в npm install
express:
vagrant $ ls
app postinstall.sh
vagrant $ cd app
vagrant $ ls
Express server.js
vagrant $ cd Express
vagrant $ npm install express

Иногда NPM и Virtual Box не работают корректно вместе на компьютере с Windows. Если
вы получаете сообщения об ошибках во время установки Express, попробуйте выполнить
команду npm install express –no-bin-links — это должно помочь.

NPM сообщит об окончании установки модуля Express и выведет длинный


список его взаимоотношений. Куда же он устанавливается? Оказывается, модуль
установлен в текущей папке, в подпапке под названием node_modules. В этом легко
убедиться:
vagrant $ pwd
/home/vagrant/app/Express
vagrant $ ls
server.js node_modules
vagrant $ ls node_modules
express

#([SUHVV
Сейчас, когда модуль Express установлен, мы можем создать простой сервер Express.
Создадим внутри каталога Express файл, назовем его server.js и добавим следующее
содержание:
Глава 6. Сервер

var express = require("express"),


http = require("http"),
app;
// Создаем http-сервер на основе Express
// и заставляем его слушать на порте 3000
app = express();
http.createServer(app).listen(3000);
// настраиваем маршруты
app.get("/hello", function (req, res) {
res.send("Hello, World!");
});
app.get("/goodbye", function (req, res) {
res.send("Goodbye, World!");
});

Этот пример кажется очень похожим на предыдущий, но на самом деле здесь


есть несколько существенных различий. Во-первых, нет необходимости прописы-
вать заголовки HTTP в обратных вызовах, так как Express уже проделал это само-
стоятельно. Во-вторых, мы просто используем res.send вместо res.write или res.end.
И последнее, но, наверное, самое важное: мы настроили два маршрута, hello и goodbye.
Мы еще увидим, что произойдет, когда будет открыт браузер, но сначала запустим
сервер.
Напомню, что мы можем сделать это, введя node server.js из папки Express на
гостевой машине. Затем, перейдя к браузеру, нужно набрать localhost:3000, как
мы уже делали ранее. В этот раз, однако, получим сообщение об ошибке с текстом
'Cannot GET /'. Но если вместо этого мы введем localhost:3000/hello или localhost:3000/
goodbye, то получим сообщения, которые указали в обратных вызовах. Попробуй-
те прямо сейчас.
Как видите, добавление hello или goodbye в конце основного URL нашего при-
ложения определяет, какая функция сработает. Кроме того, получается, что Express
не устанавливает маршрута по умолчанию. Если нужно, чтобы адрес localhost:3000
работал как прежде, нужно просто настроить главный маршрут, добавив к файлу
server.js еще три строки:
app.get("/", function (req, res) {
res.send("Это основной маршрут!");
});

Если остановить сервер (нажав Ctrl+C) и запустить его снова, мы сможем полу-
чить доступ к localhost:3000, как уже было до этого! Express обрабатывает марш-
руты самостоятельно — мы просто говорим ему, что должно быть сделано в случае
запроса определенного маршрута.


Вы уже видели, как можно отправлять информацию в браузер от сервера. Но
что, если мы хотим послать нечто вроде базовой HTML-страницы? Задание
Модули и Express

значительно усложняется, и очень быстро. Допустим, например, что надо от-


править нечто такое:
app.get("/index.html", function (req, res) {
res.send("<html><head></head><body><h1>Hello, World!</h1></body></html>");
});

Несмотря на то что это работает, отправка HTML-кода, значительно боль-


шего, чем этот простенький пример, может стать невероятно продолжительной.
К счастью, в Express эта проблема решена — мы можем использовать статич-
ный файловый сервер. С его помощью можно создавать файлы HTML, CSS
и JavaScript на клиентской стороне точно так же, как и раньше в этой книге!
Кроме того, оказывается, что для этого нужна всего одна дополнительная стро-
ка кода.
Начнем с создания новой папки в каталоге app и назовем ее client. Создадим
файл server.js такого содержания:
var express = require("express"),
http = require("http"),
app = express();
// настроим статическую файловую папку
// для маршрута по умолчанию
// (см. также замечание о маршрутах далее)
app.use(express.static(__dirname + "/client"));
// создадим HTTP-сервер на базе Express
http.createServer(app).listen(3000);
// настроим маршруты
app.get("/hello", function (req, res) {
res.send("Hello, World!");
});
app.get("/goodbye", function (req, res) {
res.send("Goodbye, World!");
});

Если вы попробуете запустить этот пример в Windows вместо своей виртуальной маши-
ны, то, скорее всего, столкнетесь с проблемой, вызванной именами файлов. Для про-
стоты изложения я отказался от использования базового модуля path, который придал
бы папке свойство кросплатформенной совместимости. В то же время я использовал его
в коде в хранилище GitHub, поэтому, если у вас возникнут какие-то трудности, ознакомь-
тесь с хранящимися там примерами.

В предыдущем примере мы использовали app.use для создания статической


файловой серверной папки. Это значит, что любой запрос, отправленный на сервер,
будет немедленно обработан статической файловой папкой (client), прежде чем
попадет на маршруты. Таким образом, если у нас есть файл index.html в папке client
и мы заходим на localhost:3000/index.html, то в результате увидим содержимое
Глава 6. Сервер

файла. Если же файла не существует, то будет произведена проверка того, есть ли


другие совпадения на маршрутах.

Здесь легко можно запутаться — если у нас есть маршрут с тем же названием, что и файл
в папке client, как же ответит Express? Сначала он ищет ответ в клиентской папке и, если
найдет совпадение, даже не будет просматривать маршруты. Поэтому нельзя создавать
файлы и маршруты с одним именем — это не приведет ни к чему хорошему практичес-
ки никогда.

Посмотрим, что будет, если мы скопируем одно из клиентских приложений в ка-


талог client. Возьмите одно из приложений, которые мы уже создали (а еще луч-
ше — поупражняйтесь и создайте по памяти новое), и скопируйте его в папку client,
находящуюся внутри Express.
Сейчас можно запустить сервер с гостевой машины с помощью команды node
server.js. Поскольку основная страница HTML находится в файле index.html,
когда мы открываем страницу localhost:3000/index.html в браузере, можем увидеть
ее — включая CSS и JavaScript!

!#
В дальнейшем будем создавать все приложения, следуя этому общему шаблону.
Браузерный код будет храниться в каталоге client, а сервер Express — в файле под
названием server.js. Мы также будем импортировать и/или создавать модули для
поддержки серверной стороны нашей программы.
Единственная вещь, с которой мы пока не сталкивались, — это настройка ком-
муникации между сервером и клиентом. Как упоминалось в предыдущей главе,
будем использовать AJAX для связи и формат JSON — для представления сообще-
ний. Следующий пример продемонстрирует это, а также настройку модулей для
получения более интересных вещей — например, подключения к Twitter API.

ø#
В этом примере мы подключимся к Twitter API и извлечем некоторые данные на
наш сервер. Начнем с запуска виртуальной машины, если она еще не работает.
Если вы пока этого не сделали, введите vagrant up из папки app, находящейся вну­
три Chapter6. Затем подключимся по SSH к гостевой машине. Если вы работаете
в Windows, это может потребовать использования PuTTY, а если в Mac OS или
Linux, просто введите vagrant ssh.
Теперь создадим новую папку внутри каталога app и назовем ее Twitter. Помни-
те, что, даже если мы создаем эту папку на гостевой машине, она отображается и на
хостовой, так что мы легко можем открыть ее (через каталог app внутри Projects)
в Sublime.
Считаем твиты

#’7ZLWWHU
Чтобы получить доступ к потоковому API Twitter, нужно установить приложение,
а затем авторизоваться, используя логин и пароль для Twitter. Конечно, если у вас
нет учетной записи в Twitter, нужно ее создать. Но не беспокойтесь, я не собираюсь
заставлять вас писать твиты!
После того как вы авторизуетесь на странице разработчиков Twitter, нажмите
кнопку Create a new application (Создать новое приложение) в верхнем правом углу.
Сделав это, вы увидите форму, похожую на рис. 6.8. Заполните все поля и отправь-
те запрос.

Рис. 6.8. Форма, которую вы будете использовать для создания первого приложения Twitter

В форме есть обязательное поле, где требуется указать веб-сайт вашего приложения.
У вас, наверное, его пока нет, поэтому можете использовать временную «заглушку»
(я, например, указываю www.example.com). Не беспокойтесь об URL обратного вызова
для этого примера.

Если запрос сработал успешно, вы окажетесь на странице, которая содержит всю


информацию о вашем приложении, включая Consumer key (ключ абонента) и Consumer
secret (секретные данные абонента). Чтобы получить доступ к потоковому API,
нужно создать маркер доступа для вашего приложения. Вы можете получить его,
нажав кнопку Create my access token (Создать мой маркер доступа). Когда страница
обновится, у вас будут все данные, необходимые для работы следующего примера.
А сейчас создадим простой файл под названием credentials.json, к которому
наша программа должна иметь доступ. Файл будет содержать один объект JSON,
в котором находятся данные для входа в Twitter (не забудьте оставить вокруг них
кавычки, так как они должны сохраниться как строковые переменные):
Глава 6. Сервер

{
"consumer_key": "your consumer key here",
"consumer_secret": "your consumer secret here",
"access_token_key": "your access token key here",
"access_token_secret": "your access token secret here"
}

Если вы создаете приложения, которые требуют данных для входа, следует изолировать
их в хранилище Git на тот случай, если вы захотите поделиться своим кодом с кем-нибудь
еще. Вот почему лучше всего хранить свои данные для входа в отдельном файле. Git
позволяет создавать файлы специального типа .gitignore — локальные файлы, которые
изолированы от остальной части хранилища.

&7ZLWWHU$3,
Начнем с установки модуля ntwitter через NPM:
vagrant $ mkdir Twitter
vagrant $ cd Twitter
vagrant $ npm install ntwitter
vagrant $ ls node_modules
express ntwitter
После этого создадим файл под названием tweet_counter.js, где будет находить-
ся код, обеспечивающий получение доступа к Twitter API. Отмечу, что мы запра-
шиваем модуль ntwitter с использованием того же пути, который использовали
при запросе модуля http в примере с сервером. Запрашивая файл credentials.json,
мы должны сообщить Node, где его найти, так как, в отличие от express и ntwitter,
не устанавливали его через NPM. Вот почему перед именем файла находит­ся ./ — это
указание Node искать файл в текущей папке:
var ntwitter = require("ntwitter"),
credentials = require("./credentials.json"),
twitter;
// настроим наш объект twitter
twitter = ntwitter(credentials);
// настроим поток twitter с тремя параметрами,
// разделенными запятыми
twitter.stream(
// первый параметр — строка
"statuses/filter",
// второй параметр — объект, содержащий массив
{ "track": ["awesome", "cool", "rad", "gnarly", "groovy" }1, //см.перевод в сноске

1
«Потрясающе», «круто», «супер», «великолепно», «классно». В данном примере
использование кириллических символов требует изменения кодировки для правильного
их отображения в командной строке. — Примеч. пер.
Считаем твиты

// третий параметр — обратный вызов, срабатывающий, когда поток создан


function(stream) {
stream.on("data", function(tweet) {
console.log(tweet.text);
});
}
);
Если все настроено правильно, мы можем сейчас ввести команду node tweet_counter
в виртуальной машине и увидим, что в командном окне отображаются твиты, содер-
жащие слова из массива! Когда вам надоест смотреть, как компьютер выражает свое
восхищение, просто нажмите Ctrl+C, чтобы остановить поток лести.

%$"
Посмотрим на код еще раз. Вы увидите несколько знакомых элементов. Во-первых,
мы объявили три переменные и импортировали модуль ntwitter вместе с файлом
credentials.json. Затем создали переменную под названием twitter и сохранили
в ней результат вызова функции ntwitter с нашими данными входа в качестве ар-
гумента. Таким образом инициализируется объект twitter, в результате чего запус-
кается поток.
После этого мы определяем поток, вызывая функцию stream для объекта twitter.
Эта функция принимает три аргумента. Первый — строка, определяющая тип по-
тока (мы представляем твиты в виде списка слов). Второй — объект, в котором
содержится информация о правилах фильтрации (в данном случае мы просто на-
блюдаем за возникновением отдельных слов, а вообще их можно отфильтровать,
например по расположению). И наконец, обратный вызов, означающий, что создан
поток, который мы отправляем.
Аргумент stream сам по себе является объектом, с помощью которого мы можем
улавливать события (аналогично элементам DOM в jQuery). Событие, которое
нужно поймать, — данные (data), и оно запускается, когда новый твит приходит из
потока. Что же мы делаем, получив новый твит? Просто вводим его в консоль! Вот
все, что заставляет Twitter отправлять поток твитов в ваше командное окно.

Вместо того чтобы просто набирать твиты в консоли, начнем сохранять количе­ство
появлений определенного слова. Это значит, что для каждого слова нужна отдельная
переменная. Таким образом получится несколько переменных, хранящих нечто
похожее, следовательно, имеет смысл использовать объект, представляющий собой
счетчик. Атрибут, связанный с каждым из счетчиков, — слово, а значение атрибу-
та — количество появлений каждого слова.
Мы можем изменить начало кода, чтобы объявить и создать такой объект:
var ntwitter = require("ntwitter"),
credentials = require("./credentials.json"),
twitter,
Глава 6. Сервер

counts = {};
counts.awesome = 0;
counts.cool = 0;
counts.rad = 0;
counts.gnarly = 0;
counts.groovy = 0;
Таким образом, начальное значение всех счетчиков будет равно нулю, потому
что пока мы не видели ни одного из этих слов.

$LQGH[2I’
Когда мы получаем данные из потока Twitter, то проверяем, содержит ли свойство
text нашего объекта tweet слово, которое мы ищем. В предыдущей главе мы видели,
что у массивов есть метод, называемый indexOf, который проверяет, находится ли
в массиве некоторый объект. Оказывается, такая же функциональность существует
и для строк! Функция indexOf для строковых объектов будет возвращать индекс для
первого появления подстроки внутри строки или –1, если подстрока не появляется.
Например, попробуйте набрать в консоли JavaScript в Chrome следующее:
var tweet = "Это твит! В нем много слов, но меньше 140 символов."
// Используем indexOf для проверки того, содержатся ли в строке отдельные слова
tweet.indexOf("твит");
//=> 10
tweet.indexOf("Это");
//=> 0
tweet.indexOf("слов");
//=> 32
tweet.indexOf("симв");
//=> 56
tweet.indexOf("привет");
//=> -1
// обратите внимание: indexOf чувствительна к регистру!
tweet.indexOf("Твит");
//=> -1
Вы видите, что indexOf будет искать твит для данного слова, и, если слово появ-
ляется в подстроке, результат будет больше –1. Это позволяет убрать оператор
console.log и модифицировать код примерно так:
function(stream) {
stream.on("data", function(tweet) {
if (tweet.indexOf("awesome") > -1) {
// приращение счетчика для слова awesome
counts.awesome = counts.awesome + 1;
}
});
}

$VHW,QWHUYDO’’
Но как мы узнаем, работает ли код, без оператора console.log? Одно из возможных
решений — введение счетчиков через каждые несколько секунд. Это более рацио-
Считаем твиты

нально, чем вывод каждого твита, который мы видим. Чтобы сделать это, можно
добавить вызов функции setInterval в нижней части потокового кода:
// вводить счетчик для
setInterval(function () {
console.log("awesome: " + counts.awesome);
}, 3000);
Аналогично функции setTimeout, с которой мы уже сталкивались, функция
setInterval устанавливает функцию, которая будет вызвана в дальнейшем. Разни-
ца состоит в том, что она повторяет вызов функции каждый раз, когда проходит
указанное количество миллисекунд. Так что в данном случае программа будет
выводить счетчик для слова awesome каждые 3 секунды.
Если нам требуется всего лишь подсчитать количество появлений слова awesome,
то целиком файл twitter.js будет выглядеть следующим образом:
var ntwitter = require("ntwitter"),
credentials = require("./credentials.json"),
twitter,
counts = {};
// настроим объект twitter
twitter = ntwitter(credentials);
// обнуляем счетчики
counts.awesome = 0;
twitter.stream(
"statuses/filter",
{ "track": ["awesome", "cool", "rad", "gnarly", "groovy"] },
function(stream) {
stream.on("data", function(tweet) {
if (tweet.indexOf("awesome") > -1) {
// приращение счетчика для слова awesome
counts.awesome = counts.awesome + 1;
}
});
}
);
// вводим счетчик каждые 3 секунды
setInterval(function () {
console.log("awesome: " + counts.awesome);
}, 3000);
Запустите этот код на своей виртуальной машине, введя node tweet_counter.js,
и посмотрите на него в действии.

ł7ZLWWHU
Наблюдать за появлением твитов в командном окне, конечно, забавно, но было бы
лучше каким-то образом подключить счетчик твитов к серверу HTTP, чтобы мы
могли отображать их в браузере. Есть два способа сделать это. Первый заключает-
ся в создании внутри кода счетчика твитов сервера, который и будет генерировать
страницу. Это решение вполне подходит для начинающих.
Глава 6. Сервер

Второй способ заключается в преобразовании кода счетчика твитов в модуль,


а затем импорте этого модуля в программу, где уже есть сервер HTTP (таков, на-
пример, наш основной серверный пример). В данном случае второе решение лучше,
так как оно позволяет использовать этот код счетчика твитов и в других проектах.
Я бы сказал, что это решение соответствует одному из ключевых принципов фи-
лософии Node.js: создание маленьких модулей, каждый из которых делает какую-
то одну вещь, но делает ее хорошо.
Как мы можем преобразовать счетчик твитов в модуль? Оказывается, это очень
просто. В каждом модуле есть специальная переменная под названием module.exports,
где хранится все, что нужно представлять окружающему миру. В данном случае
нам следует представить объект counts, который будет обновляться во время рабо-
ты нашей программы. В нижней части кода tweet_counter.js мы можем просто до-
бавить следующую строку для экспорта объекта:
module.exports = counts;

Здесь мы экспортируем объект, где хранятся несколько целочисленных переменных, но


вы можете экспортировать любой тип объекта JavaScript, включая функции. В практи-
ческих упражнениях в конце этой главы я дам вам возможность сделать этот модуль
более универсальным с помощью экспортирования функции вместо объекта

’([SUHVV
Напомню, что наш несложный сервер Express выглядит примерно так:
var express = require("express"),
http = require("http"),
app = express();
// настраиваем приложение для использования клиентской папки для статичных файлов
app.use(express.static(__dirname + "/client"));
// создаем HTTP-сервер на базе Express и начинаем слушать
http.createServer(app).listen(3000);
// настраиваем маршруты
app.get("/hello", function (req, res) {
res.send("Hello, World!");
});
Немного изменим этот код так, чтобы импортировать модуль для подсчета твитов
и использовать полученные результаты, возвращая их в браузер через JSON:
var express = require("express"),
http = require("http"),
tweetCounts = require("./tweet_counter.js"),
app = express();
// настраиваем приложение для использования клиентской папки для статичных файлов
app.use(express.static(__dirname + "/client"));
Считаем твиты

// создаем HTTP-сервер на базе Express и начинаем слушать


http.createServer(app).listen(3000);
// настраиваем маршруты
app.get("/counts.json", function (req, res) {
// res.json возвращает весь объект целиком в виде файла JSON
res.json(tweetCounts);
});
Если мы авторизуемся на гостевой машине, то можем ввести папку app и за-
пустить сервер из node app. Сделав это, мы увидим вывод счетчиков в командной
строке каждые 3 секунды, но сейчас можем открыть браузер и подключиться
к localhost:3000/counts.json, после чего увидим отображение JSON объекта!
Но как взаимодействовать с объектом JSON в клиентском коде? Конечно же,
через AJAX!

ı
Напишем основу клиентского приложения, которая, как обычно, будет включать
в себя файлы index.html, styles/style.css и javascripts/app.js, к которым мы уже
привыкли при программировании клиентской стороны. Создайте их непосред­
ственно в папке client или в любом другом месте, а затем скопируйте в client с помо-
щью команды cp. Запустите сервер из каталога app на гостевой машине:
vagrant $ node app.js
Если в файле javascript/app.js уже настроен вывод hello, world, то, перейдя на
localhost:3000/index.html в браузере, мы увидим слова «Hello, World!», выведенные
в консоли JavaScript, как и раньше.
Если все работает правильно, изменим файл клиентской стороны app.js для
получения объекта counts.json с вызовом функции jQuery getJSON:
var main = function () {
"use strict";
$.getJSON("/counts.json", function (wordCounts) {
// Сейчас "wordCounts" становится объектом, возвращаемым
// маршрутом counts.json, который мы
// настроили в Express
console.log(wordCounts);
});
}
$(document).ready(main);
Таким образом, вызов getJSON подключается к маршруту counts.json, который
мы настроили в Express, в результате чего нам возвращаются счетчики. Если все
эти связи работают правильно, в консоль теперь будет выводиться объект counts.
Мы можем просмотреть результат вывода объекта сверху вниз с помощью кнопок
прокрутки. А если перезагрузим страницу, то увидим обновленные значения!
Сейчас мы можем модифицировать код для вставки счетчиков в DOM с помо-
щью инструментов jQuery для управления объектами DOM. Вот простой способ
добиться этого:
Глава 6. Сервер

var main = function () {


"use strict";
var insertCountsIntoDOM = function (counts) {
// здесь код для управления элементами DOM
};
// обратите внимание: insertCountsIntoDOM вызывается
// с параметрами счетчиков, когда getJSON возвращает их
$.getJSON("counts.json", insertCountsIntoDOM);
}
$(document).ready(main);

Почему это сделано именно так? Потому, что такой способ реализации позво-
ляет нам проделать нечто похожее на недавние операции с кодом на серверной
стороне — использовать функцию setInterval, чтобы обновлять страницу динами-
чески:
var main = function () {
"use strict";
var insertCountsIntoDOM = function (counts) {
// здесь код для управления элементами DOM
};
// проверка значения counts каждые 5 секунд
// и вставка обновленных значений в DOM
setInterval(function () {
$.getJSON("counts.json", insertCountsIntoDOM);
}, 5000);
}
$(document).ready(main);

Попробуйте сами поиграть с этим кодом, перенастроить его и действительно


разобраться, как это работает. Это первый пример полноценного веб-приложения,
которое имеет и клиентский, и серверный компоненты, связанные друг с другом
с помощью AJAX.

ø’$PD]HULI¿F
Если вы как следует поработали с примером из предыдущей главы, то вполне
справитесь с организацией работы приложения Amazeriffic так, чтобы оно обраба-
тывало список задач, передаваемый файлом JSON, а не жестко прописанный в про-
граммном коде. На этом примере я предлагаю вам начать с работы с хранилищем
Git, чтобы отслеживать изменения, появляющиеся по ходу работы.

ı
Начнем с создания папки внутри каталога Chapter5/app, которую назовем Amazeriffic.
Напомню, что не имеет значения, сделаем мы это на гостевой или на хостовой маши-
не. Внутри папки создадим еще одну для хранения клиентского кода. Назовем ее
client и скопируем туда содержимое предыдущего примера с Amazeriffic из главы 5.
Создание сервера для Amazeriffic

Очень важное различие между этим примером и примером из главы 5 состоит


в том, что мы будем хранить список задач на сервере. Это значит, что нам больше не
нужен файл todo.json с начальным содержанием списка задач. Но, поскольку через
некоторое время нам потребуется скопировать содержание этого файла на сервер,
лучше всего сохранить его под другим именем. Чтобы переименовать файл из коман-
дной строки, используйте простую команду mv, что значит move («переместить»):
hostname $ mv client/todos.json client/todos.OLD.json

ø!*LW
Затем заложим основу хранилища Git, отправив туда скопированные файлы. Пом-
ните, что взаимодействовать с Git нужно с хостовой машины. С этого момента я пре-
доставляю вам выбирать подходящие моменты для выполнения коммитов:
hostname $ git init
Initialized empty Git repository ...

После этого мы можем проверить статус хранилища и отправить туда файлы


клиентской стороны, как мы это делали в предыдущих главах. Затем можем пере-
ключиться на гостевую машину, чтобы установить модуль Express. Перейдите
в каталог Amazeriffic и наберите следующую команду npm:
vagrant $ pwd
/home/vagrant/app/Amazeriffic
vagrant $ npm install express

ø
Сейчас наше приложение работает отлично, если исходить из предположения, что
пользователь никогда не переходит в другой браузер или на другой компьютер и ни-
когда не перезагружает страницу. К сожалению, если пользователь предпримет одно
из этих действий, он потеряет все созданные задачи и вернется к исходной точке.
Мы решаем эту проблему, отправляя список задач на хранение на сервере и пе-
ренастраивая клиент для обработки данных с сервера. Для начала создадим список
задач в переменной на сервере и настроим маршрут JSON для их доставки:
var express = require("express"),
http = require("http"),
app = express(),
toDos = {
// настраиваем список задач копированием
// содержимого из файла todos.OLD.json
};
app.use(express.static(__dirname + "/client"));
http.createServer(app).listen(3000);
// этот маршрут замещает наш файл
// todos.json в примере из главы 5
Глава 6. Сервер

app.get("todos.json", function (req, res) {


res.json(toDos);
});

Проверьте, что находитесь на гостевой машине, и запустите app.js через Node:


vagrant $ node app.js
После запуска мы сможем зайти на страничку нашего приложения, открыв
Chrome и введя localhost:3000/index.html в адресную строку! На самом деле мы
можем просто набрать localhost:3000 в адресной строке, после чего сервер автома-
тически перенаправит нас к файлу index.html, который, как правило, считается
страницей по умолчанию.
На этот момент все должно работать точно так же, как раньше, но мы пока не
решили ни одну из заявленных ранее проблем. Это потому, что, хотя список задач
и доставляется с сервера, клиент пока не посылает никаких обновлений. А мы пока
не изучили, как отправлять информацию на сервер в случае, если что-то меняется
на клиенте.

ł!
До этого момента в программировании на клиентской стороне мы ограничивались
получением данных с сервера. Это значит, что наши приложения поддерживали
только одностороннюю коммуникацию между сервером и клиентом. Для обеспе-
чения этой коммуникации мы пользовались функцией jQuery под названием
getJSON. Оказывается, в jQuery существует и такая функция, которая позволяет
легко отправлять на сервер объекты JSON, но для этого нужно модифицировать
серверную сторону и подготовить ее к приему данных.
Процесс отправки данных с клиента на сервер через протокол HTTP называет-
ся post. Начнем с настройки маршрута post на сервере Express. Следующий пример
настроит маршрут post, который просто выводит строку в терминале сервера:
app.post("/todos", function (req, res) {
console.log("Данные были отправлены на сервер!");
// простой объект отправлен обратно
res.json({"message":"Вы размещаетесь на сервере!"});
});

Это очень похоже на маршруты get, за исключением того, что мы заменили


вызов функции get вызовом функции call. На практике разница состоит в том, что
мы не можем просто проложить маршрут, используя браузер, как делали с марш-
рутами get. Вместо этого нужно изменить JavaScript на клиентской стороне, раз-
местив в нем маршрут, чтобы увидеть сообщение. Мы делаем это, когда пользова-
тель щелкает на кнопке Добавить. Модифицируем клиентский код следующим
образом:
Создание сервера для Amazeriffic

// этот код находится внутри функции main,


// где изначально был определен toDoObjects
$button.on("click", function () {
var description = $input.val(),
tags = $tagInput.val().split(",");
toDoObjects.push({"description":description, "tags":tags});
// здесь мы отправляем быстрое сообщение на маршрут списка задач
$.post("todos", {}, function (response) {
// этот обратный вызов выполняется при ответе сервера
console.log("Мы отправили данные и получили ответ сервера!");
console.log(response);
});
// обновление toDos
toDos = toDoObjects.map(function (toDo) {
return toDo.description;
});
$input.val("");
$tagInput.val("");
});
Первый аргумент функции $.post — маршрут, на который мы хотим отправить
данные, второй — сами данные (представленные в виде объекта), которые нужно
послать, и третий — обратный вызов для ответа сервера. Сейчас наш код должен ра-
ботать точно так же, как раньше, но при щелчке на кнопке Добавить мы увидим, что
сервер выводит сообщение в своей консоли, а клиент выводит ответ сервера. Так что
мы на правильном пути, но пока что не отправили список задач на сервер. Как я уж
говорил, второй аргумент функции post — это объект, который отправляется на сервер.
Оказывается, очень просто заменить пустой объект новым объектом со списком задач,
но на сервере придется проделать несколько больше работы для его использования.
Самый простой путь добиться этого — использовать URL-шифрованный плагин
Express, который будет превращать JSON, отправленный jQuery, в объект JavaScript,
который сможет использовать сервер:
var express = require("express"),
http = require("http"),
app = express()
toDos = {
// ...
};;
app.use(express.static(__dirname + "/client"));
// командуем Express принять поступающие
// объекты JSON
app.use(express.urlencoded());
app.post("/todos", function (req, res) {
// сейчас объект сохраняется в req.body
var newToDo = req.body;
console.log(newToDo);
toDos.push(newToDo);
// отправляем простой объект
res.json({"message":"Вы разместили данные на сервере!"});
});
Глава 6. Сервер

На этом этапе сервер будет выводить объект, когда он отправлен, в серверную


консоль. После этого он будет добавлять новую задачу в список. А сейчас сделаем
еще одно небольшое изменение, чтобы наше клиентское приложение действитель-
но добавляло новый элемент в список задач:
$button.on("click", function () {
var description = $input.val(),
tags = $tagInput.val().split(","),
// создаем новый элемент списка задач
newToDo = {"description":description, "tags":tags};
$.post("todos", newToDo, function (result) {
console.log(result);
// нужно отправить новый объект на клиент
// после получения ответа сервера
toDoObjects.push(newToDo);
// обновляем toDos
toDos = toDoObjects.map(function (toDo) {
return toDo.description;
});
$input.val("");
$tagInput.val("");
});
});
Таким образом новый элемент списка задач будет отправлен на сервер, а сооб-
щение с ответом сервера — выведено в консоли JavaScript. После всего этого новый
объект toDo обновит список задач на клиентской стороне.
Теперь мы можем открыть приложение в двух разных окнах браузера, добавить
новый элемент списка задач в одном окне, а затем перезагрузить страницу во вто-
ром, чтобы увидеть обновленное содержание!

В этой главе мы изучили основы программирования на серверной стороне с помо-


щью Node.js. Node.js — это платформа, которая позволяет программировать сервер
на языке JavaScript. В частности, мы используем его для написания серверов HTTP,
которые связываются с клиентским приложением, запущенным в браузере.
Программы Node.js, как правило, состоят из нескольких отдельных модулей.
Мы можем создавать собственные модули, написав какой-либо код, а затем выде-
лив нужные части в объекте module.exports. После этого мы можем импортировать
их в другую программу с помощью функции require.
NPM означает Node.js Package Manager (менеджер пакетов Node.js) и служит
для установки модулей Node.js, которые мы не писали самостоятельно и которые
не включены в дистрибутив Node.js. Например, модуль Express мы установили с по-
мощью NPM. Express — это оболочка модуля http в Node.js, включающая в себя
большинство наиболее частых требований к серверу HTTP. В частности, она поз-
воляет легко доставлять файлы клиентской стороны — HTML, CSS и JavaScript.
Больше теории и практики

Кроме того, с ее помощью можно настраивать специфические маршруты для обра-


ботки данных сервером.
В качестве базового окружения мы использовали VirtualBox и Vagrant. Эти
инструменты не являются необходимыми для понимания работы веб-приложения,
но облегчают запуск процесса разработки. Кроме того, с их помощью можно четко
разделить клиент (браузер, запущенный на хостовой машине) и сервер (программу
Node.js, запущенную на гостевой машине).
И последнее по порядку, но не по значимости: мы научились настраивать за-
просы HTTP post c помощью jQuery, а также заставлять сервер отвечать на запро-
сы post через Express.

Ø$  
$’1RGHMV
В этой главе мы установили Node.js на виртуальной машине с помощью Vagrant.
Основная причина этого состоит в том, что сценарии Vagrant одновременно на-
строили программное обеспечение, которое понадобится нам в главах 7 и 8. Одна-
ко установить Node.js на локальном компьютере очень, очень просто, и все приме-
ры из этой главы будут работать на вашей локальной машине с минимальными
изменениями. Если вы зайдете на http://nodejs.org/download, то сможете найти уста-
новочные пакеты для своей платформы.
Вам очень полезно будет попробовать сделать это. Если вы установите ПО на
своей хостовой машине, то сможете инсталлировать и несколько других полезных
инструментов, включая JSHint, CSS Lint и валидатор HTML5.

-6+LQW&66/LQW130
В главе 4 мы познакомились с JSLint — инструментом для контроля качества кода,
который проверяет соответствие кода стандартам качества JavaScript. Мы исполь-
зовали сетевой инструмент, доступный по адресу http://jslint.com, где нужно было
копировать и вставлять код в браузер. Пока это не вызывало никаких трудностей,
но с разрастанием кода работать с сайтом станет неудобно.
Оказывается, что если Node.js установлен на локальной машине, то с помощью
NPM можно установить другие инструменты с интерфейсом командной строки,
которые могут сделать работу очень удобной, например JSHint. JSHint очень
похож на JSLint и также может использоваться для проверки вашего кода. Прой-
дите в командную строку и установите его с помощью NPM. Мы можем исполь-
зовать флажок –g, чтобы указать NPM, что хотим установить пакет глобально.
Таким образом, мы сможем использовать JSHint как стандартное приложение
с интерфейсом командной строки вместо установки его в качестве библиотеки в ка-
талоге node_modules:
hostname $ sudo npm install jshint -g
Глава 6. Сервер

В Mac OS и Linux нужно использовать sudo для установки пакетов глобально. В Windows
это не обязательно.

Сделав это, мы можем запустить JSHint непосредственно для обработки каких-


либо файлов. Допустим, у нас есть файл под названием test.js:
var main = function () {
console.log("hello, world");
}
$(document).ready(main);

Этот код, конечно, будет отлично работать в браузере, но в нем пропущена точ-
ка с запятой после определения функции main. JSHint предупредит об этом:
hostname $ jshint test.js
test.js: line 3, col 2, Missing semicolon.1
1 error

Если же с кодом все в порядке, JSHint не даст никакого ответа. Если мы добавим
пропущенную точку с запятой и запустим JSHint снова, то не увидим никакого
сообщения:
hostname $ jshint test.js
hostname $
Установка JSHint на локальной машине делает проверку качества кода (как
серверного, так и клиентского) гораздо более простой, так что нет причин этого не
делать. Вы также обнаружите, что JSHint может быть очень гибким, позволяя лег-
ко менять опции проверки с помощью командной строки. Документация находит-
ся по адресу http://jshint.com/docs.
Аналогично NPM позволяет установить и запустить CSS Lint из командной
строки, что делает проверку CSS на наличие ошибок и соответствие стандартам
качества очень простой:
hostname $ sudo npm install csslint –g
Для валидаторов и проверочных средств HTML тоже есть несколько функций.
Если вам придется много работать с HTML, вы легко найдете необходимые для
этого инструменты.

Наш счетчик твитов отслеживает массив слов в Twitter. Однако в коде мы долж-
ны перечислять слова в нескольких разных местах. Например, в начале файла
tweet_counter.js видим следующее:

1
Сообщение: «test.js: строка 3, столбец 2, пропущена точка с запятой». — Примеч. пер.
Больше теории и практики

counts.awesome = 0;
counts.cool = 0;
counts.rad = 0;
counts.gnarly = 0;
counts.groovy = 0;
А позже видим указание модулю ntwitter отслеживать эти слова:
{ "track": ["awesome", "cool", "rad", "gnarly", "groovy"] },
Если вы уже пытались оптимизировать этот код, то, возможно, сделали нечто
похожее для приращения счетчиков:
if (tweet.indexOf("awesome") > -1) {
// приращение счетчика awesome
counts.awesome = counts.awesome + 1;
}
if (tweet.indexOf("cool") > -1) {
// приращение счетчика cool
counts.cool = counts.cool + 1;
}
Что здесь плохого? Если нужно удалить или добавить какое-нибудь слово, то
придется изменять код в трех местах! Как можно решить эту проблему? Для нача-
ла можно определить массив в одном определенном месте — в начале модуля tweet_
counter:
var trackedWords = ["awesome", "cool", "rad", "gnarly", "groovy"];

Таким образом, при создании счетчика твитов мы можем использовать пере-


менную:
{ "track": trackedWords };

В оставшейся части кода нам пригодится полезнейшее свойство JavaScript —


возможность использовать объект как схему (map) структуры данных, где атрибу-
тами являются строки. Эквивалентными будут два следующих способа индексации
в объект:
// доступ к счетчику awesome
// получен через оператор точка (dot)
counts.awesome = 0;
counts.awesome = counts.awesome + 1;
// доступ к счетчику awesome
// получен через строку
counts["awesome"] = 0;
counts["awesome"] = counts["awesome"] + 1;

В общем случае стоит применить способ с точкой, так как большинство про-
граммистов JavaScript сочтут его лучше читаемым (да и JSLint будет с этим согла-
сен). Но у способа со строкой и квадратными скобками есть более весомое преиму-
щество — мы можем использовать переменные для доступа к значениям:
var word = "awesome";
Глава 6. Сервер

counts[word] = 0;
counts[word] = counts[word] + 1;
Вы догадываетесь, к чему я веду? Мы можем добиться того, чтобы объект счет-
чика зависел исключительно от массива:
// создается пустой объект
var counts = {};
trackedWords.forEach(function (word) {
counts[word] = 0;
});
Цикл перебирает каждое слово и устанавливает начальное значение счетчика
равным нулю. Аналогичным образом с помощью цикла forEach мы можем оптими-
зировать код, проверяющий, содержит ли твит какое-либо слово. Сделав это, мы
можем легко добавлять слова в изначальный массив и удалять их из него, а прило-
жение будет обновляться!
Это сделало наш код лучше управляемым, но мы можем улучшить его еще не-
множко. Представьте, что мы позволим пользователю нашего модуля вместо экс-
порта счетчиков для определенных слов самостоятельно решать, какие слова будут
отслеживаться. Например, пользователь модуля может захотеть использовать его
как-то так:
var tweetCounter = require("./tweet_counter.js"),
counts;
// таким образом, наш tweetCounter запускается для
// определенных слов вместо жестко прописанного списка
counts = tweetCounter(["hello", "world"]);
Мы можем добиться этого, экспортировав функцию вместо объекта counts:
var setUpTweetCounter = function (words) {
// настраиваем объект counts
// и поток ntwitter
// для использования массива слов
// ...
// а в конце возвращаем счетчик
return counts;
}
module.exports = setUpTweetCounter;
Таким образом, в перспективе наш модуль становится более гибким в приме-
нении. Программа, которая использует модуль, может решать самостоятельно,
какие слова будут отслеживаться, даже не заглядывая в код. Эта настройка — от-
личное упражнение, и вам стоит попробовать выполнить ее!

$3,’
Вот несложный проект, который позволит вам попрактиковаться в создании API
с помощью Express. Создайте приложение Express, которое отвечает на единичный
маршрут получения данных /hand. Этот маршрут должен принимать объект, озна-
Больше теории и практики

чающий раздачу карт, а затем отвечать объектом JSON, который содержит лучшую
из имеющихся комбинаций. Например, получен следующий объект:
[
{ "rank":"двойка", "suit":"пик" },
{ "rank":"четверка", "suit":"червей" },
{ "rank":"двойка", "suit":"треф" },
{ "rank":"король", "suit":"пик" },
{ "rank":"восьмерка", "suit":"бубен" }
]

Наше API с помощью сообщения null (мы говорили о нем в главе 5), означаю-
щего отсутствие объекта, может ответить вот так:
{
"handString":"пара",
"error": null
}

Другими словами, в этом примере мы отправили корректную раздачу (пять


карт), а максимальная комбинация, которую можно из нее состряпать, — пара. Мы
установили для свойства error значение null, означающее, что ошибок здесь нет.
В случае же, если кто-то отправит некорректную раздачу (например, с неправиль-
ными номиналами карт или слишком большим количеством карт), мы установим
значение handString на null и отправим обратно сообщение об ошибке:
{
"handString": null,
"error": "Раздача некорректна!"
}
Для достижения всего этого написанные функции следует выделить в модуль
Node.js, как описано в главе 5. Для использования в приложении его нужно импор-
тировать на Express-сервер:
var poker = require("./poker.js");
А затем обработать функции следующим образом:
var hand = [
{ "rank":"двойка", "suit":"пик" },
{ "rank":"четверка", "suit":"червей" },
{ "rank":"двойка", "suit":"треф" },
{ "rank":"король", "suit":"пик" },
{ "rank":"восьмерка", "suit":"бубен" }
]
var hasPair = poker.containsPair(hand);
// hasPair сейчас будет true
Как это сделать? Создадим в определении модуля объект poker с несколькими
функциями:
var poker = {};
poker.containsPair = function (hand) {
Глава 6. Сервер

// ... определение функции


}
poker.containsThreeOfAKind = function (hand) {
// ... определение функции
}
module.exports = poker;
Модуль poker будет иметь несколько встроенных функций, которые мы не ста-
нем включать в экспортированный объект. Например, мы включили функцию
containsNTimes в модуль, но не экспортируем ее:
var poker = {},
containsNTimes; // объявление функции
// здесь мы определяем наши "приватные" функции,
// которые не будут добавлены в объект poker
containsNTimes = function (array, item, n) {
// ... определение функции
};
poker.containsPair = function (hand) {
// ... используйте здесь containsNTimes
}
poker.containsThreeOfAKind = function (hand) {
// ... используйте здесь containsNTimes
}
// экспортируем только функции, относящиеся к покеру
module.exports = poker;

Может понадобиться включить еще несколько функций для проверки того, что
раздача действительно является покерной, и даже специальную функцию для
возврата объекта, указанного API. Таким образом, обратный вызов маршрута, по
сути, уместится в одной строке:
app.post("/hand", function (req, res) {
var result = poker.getHand(req.body.hand);
res.json(result);
});

Чтобы все это работало, нужно написать клиентское приложение, создающее


несколько раздач и отправляющее их на сервер с помощью jQuery. Если вам удаст­
ся заставить все это работать, ваше обучение действительно начинает приносить
плоды!
#

В нескольких предыдущих главах мы научились создавать несложные серверы


с помощью Node.js, а также обмениваться данными между сервером и клиентом
с помощью AJAX. Одно из заданий, которые мы выполняли, включало в себя от-
слеживание количества упоминаний различных слов в ленте Twitter и отображение
счетчика на клиентской стороне.
Одна из основных проблем этого приложения заключается в том, что всю ин-
формацию, необходимую для работы, оно хранит в памяти программы Node.js. Это
значит, что если мы прервем серверный процесс, то одновременно исчезнут и счет-
чики чисел. Это весьма нежелательно, потому что сервер придется останавливать
часто, например, чтобы обновить его, а скорее, сервер может остановиться сам по
причине имеющегося бага. Было бы хорошо, если бы полученные нами счетчики
не повреждались, когда происходит что-то из названного.
Чтобы решить эту проблему, нужно специальное приложение для хранения
данных, работающее независимо от программы. В этой главе мы изучим два раз-
личных способа решения данной проблемы с использованием хранилищ данных
в формате не-SQL. В частности, познакомимся с Redis и MongoDB, а также рас-
смотрим, как взаимодействовать с ними в приложениях Node.js.

64/
64/
Если вы изучали базы данных, то, наверное, вам знакома аббревиатура SQL (ино­гда
произносится как sequel1), что значит Structured Query Language (структурирован-
ный язык запросов). Это язык, используемый для запросов к базе, в которой данные
хранятся в реляционном формате. Реляционные базы данных хранят данные в ячей-
ках таблиц, в них таблицы легко связываются с другими таблицами с помощью
перекрестных ссылок. Например, в реляционной базе данных может храниться

1
Читается «сиквел», на русском языке тоже так говорят, но редко. — Примеч. пер.
Глава 7. Хранение данных

таблица фамилий актеров и актрис, а также отдельная таблица с названиями филь-


мов. А реляционная таблица будет использоваться для связи актеров и актрис
с фильмами, в которых они играли.
Данный подход имеет определенные преимущества, но главное, почему такие
базы очень широко используются, — это то, что хранение информации таким
способом минимизирует дублирование данных и, следовательно, требует меньше
пространства, чем альтернативные подходы. В прошлом это было очень важно, так
как хранение большого количества данных стоило очень дорого. Но в последние
годы цена хранения данных многократно снизилась, поэтому минимизация избы-
точности данных уже не так важна, как раньше.
По этой причине исследователи и инженеры пересмотрели свои убеждения
относительно хранения данных и начали экспериментировать с хранением в не-
реляционном виде. Новые способы хранения данных, иногда называемые не-SQL
(NoSQL), обеспечили компромисс: так, можно хранить избыточную информацию,
но при этом повысить простоту использования с точки зрения программирования.
Более того, некоторые из этих хранилищ данных были разработаны специально
для особых сценариев использования, например для приложений, где чтение дан-
ных должно быть эффективнее записи.

5HGLV
Redis — великолепный пример хранения данных в формате не-SQL. Он был раз-
работан для быстрого доступа к данным, которые часто используются. Это уско-
рение достигается за счет надежности, так как данные хранятся в памяти, а не на
диске (технически Redis периодически выполняет сохранение на диск, но в основ-
ном вы можете думать о нем как о хранилище данных в памяти).
Redis хранит информацию в формате «ключ — значение» — вы, возможно, уви-
дите здесь аналогию со способом, который использует JavaScript для хранения
свойств объекта. Таким образом разработчик получает возможность организовать
хранение данных традиционной структуры (хэш, списки, наборы и т. д.), то есть
в виде естественного продолжения хранения данных в программе. Это отличное
решение для данных, к которым необходим быстрый доступ, или для временного
хранения часто используемой информации (это называется кэшированием), оно
улучшает время отклика наших приложений.
Мы не будем изучать эти сценарии в данной книге, но не забывайте о них во
время чтения. Использовать Redis мы будем довольно примитивным образом, так
как нам нужно всего лишь сохранить счетчики Twitter отдельно от сервера Node.js.
Для этого присвоим каждому слову ключ, а все значения будут целочисленными
переменными, представляющими собой количество раз использования каждого
слова. Перед тем как научиться программировать это, поговорим о взаимодействии
с Redis с помощью командной строки.
Redis

Π5HGLV&
&
Чтобы провести черту под предыдущей работой, начнем с копирования проекта
node-dev-bootstrap в папку Projects, создав папку Chapter7. Если вам нужно вспомнить,
как это сделать, обратитесь к главе 6.
Покончив с копированием, войдите в папку, запустите виртуальную машину
и подключитесь к гостевой машине. Поскольку вы будете восстанавливать машину
с нуля, это займет некоторое время:
hostname $ cd Projects/Chapter7
hostname $ vagrant up
...vagrant build stuff...
hostname $ vagrant ssh
Сейчас вы должны быть авторизованы на виртуальной машине, где уже инстал-
лирован и настроен Redis. Можно использовать команду redis-cli для старта ин-
терактивного клиента Redis:
vagrant $ redis-cli
redis 127.0.0.1:6379>
Хранить данные в Redis так же просто, как использовать команду set. Здесь мы
создадим ключ для слова awesome и установим его значение равным 0:
redis 127.0.0.1:6379> set awesome 0
OK
Если все идет хорошо, Redis должен ответить «ОК». Мы можем проверить зна-
чение ключа с помощью команды get:
redis 127.0.0.1:6379> get awesome
"0"
Получив сохраненное значение, мы можем делать с ним очень многое. Конечно,
больше всего мы заинтересованы в его приращении на единицу, когда встречается
слово, использованное в твите. К счастью, оказывается, что в Redis есть команда
incr (от increment — «прирастить»), которая делает именно это:
redis 127.0.0.1:6379> incr awesome
(integer) 1
redis 127.0.0.1:6379> incr awesome
(integer) 2
redis 127.0.0.1:6379> get awesome
"2"
Команда incr добавляет 1 к величине, которая в настоящее время ассоциирова-
на с определенным ключом (в данном случае ключ — awesome). Чтобы выйти из
интерактивного клиента Redis, мы можем набрать exit.
Эти три команды (set, get, incr) — все, что нам нужно знать, чтобы приступить
к организации хранения счетчиков, но на самом деле возможности Redis гораздо
Глава 7. Хранение данных

шире, чем описанные мной. Чтобы попробовать некоторые из функций, изучите


великолепный интерактивный учебник по Redis, домашняя страница которого
изображена на рис. 7.1.

ß’5HGLVSDFNDJHMVRQ
К этому моменту мы уже знаем, как создать ключ для каждого слова, а затем ин-
терактивно наращивать его значение, но хотелось бы проделать это с помощью
программирования Node.js из наших приложений.

Рис. 7.1. Домашняя страница интерактивного учебника по Redis

Будем работать с проектом о Twitter из предыдущей главы, поэтому скопируй-


те папку Twitter из Chapter6/app в Chapter7/app. Вы можете использовать команду cp
для выполнения этой задачи из командной строки или просто воспользоваться
обычным способом через графический интерфейс.
Чтобы заставить программу Node.js взаимодействовать с Redis, нужно уста-
новить модуль node-redis. Раньше мы из командной строки использовали коман-
ду npm для установки модуля ntwitter. В этом был смысл в начале работы, но,
продвигаясь вперед, важно отслеживать взаимозависимости более формальным
путем. Для этого используем специальный файл package.json, который отслежи-
вает изменения различных аспектов вашего проекта, включая их взаимозависи-
мости.
В папке app создайте файл с именем package.json и скопируйте в него следующий
код:
Redis

{
"name": "tutorial",
"description": "a tutorial on using node, twitter, redis, and express",
"version": "0.0.1",
"dependencies": {
"ntwitter": "0.5.x",
"redis": "0.8.x"
}
}

NPM предлагает интерактивный способ создания файла package.json, но вам придется


ответить на множество вопросов о нем. Если хотите попробовать, наберите npm init,
находясь в папке вашего проекта.

Как видите, таким образом указаны несколько деталей о нашем проекте, вклю-
чая его взаимозависимости (поскольку для Node.js рекомендуется клиент node-redis,
он просто обращается к redis в NPM). Мы используем этот способ в главе 8, чтобы
поместить приложение и все его взаимозависимости в Cloud Foundry. А сейчас
package.json нужен нам только для упрощения установки. В частности, мы можем
сейчас инсталлировать все взаимозависимости, набрав следующую команду на
гостевой машине:
vagrant $ cd app
vagrant $ npm install

Œ5HGLV
На хостовой машине с помощью текстового редактора откройте файл tweet_counter.js,
который мы создали в предыдущей главе. Он должен находиться в папке Twitter
в каталоге app. Если вы работали с примерами и задачами, ваш код, должно быть,
лучше проработан, чем мой. В любом случае следуйте за мной и модифицируйте
код для импорта и использования модуля Redis:
var ntwitter = require("ntwitter"),
redis = require("redis"), // требование модуля redis
credentials = require("./credentials.json"),
redisClient,
twitter,
// объявление объектов счетчиков для хранения счетчиков
counts = {};
twitter = ntwitter(credentials);
// создание клиента для подключения к Redis
client = redis.createClient();
// инициализация счетчиков
counts.awesome = 0;
twitter.stream(
Глава 7. Хранение данных

"statuses/filter",
{ track: ["awesome", "cool", "rad", "gnarly", "groovy"] },
function(stream) {
stream.on("data", function(tweet) {
if (tweet.text.indexOf("awesome") >= -1) {
// приращение ключа на клиенте
client.incr("awesome");
counts.awesome = counts.awesome + 1;
}
});
}
);
module.exports = counts;

Мы использовали функцию incr аналогично тому, как делали это с интерактив-


ным клиентом Redis. Фактически мы можем использовать любую из команд Redis
тем же способом — клиент предоставляет функцию с тем же самым названием
команды.
Мы можем запустить этот код обычным способом. Дайте ему поработать немно-
го — он должен продолжать отслеживать количество твитов, содержащих слово
awesome в Redis, и, если код setInterval остался на месте, мы увидим, что периоди-
чески он выводит значения. Убедившись, что код обработал несколько твитов, мы
можем проверить, что счетчики хранятся в Redis, остановив программу (нажатием
Ctrl+C), а затем повторно подключившись к redis-cli. После этого можно исполь-
зовать команду get , чтобы увидеть сохраненное значение упоминаний слова
awesome:
vagrant $ redis-cli
redis 127.0.0.1:6379> get awesome
(integer) "349"

Если вдруг вы захотите очистить хранилище данных Redis, можете сделать это в любой
момент. Нужно набрать команду flushall в командной строке redis-cli.

ß$’
!5HGLV
Сейчас наши данные сохраняются в течение работы программы, но, перезапуская
сервер, мы все еще обнуляем объект count. Для окончательного решения этой про-
блемы нужно, чтобы счетчик твитов устанавливал в качестве начального значения
данные, которые сохранены в Redis. Мы можем использовать команду get, чтобы
получить их перед тем, как запустим поток. Но, как и многое в Node, функция get
является асинхронной, поэтому работать с ней надо осторожно:
Redis

var ntwitter = require("ntwitter"),


redis = require("redis"), // требование модуля Redis
credentials = require("./credentials.json"),
redisClient,
counts = {},
twitter;
twitter = ntwitter(credentials);
client = redis.createClient();
// обратный вызов получает два аргумента
client.get("awesome", function (err, awesomeCount) {
// проверка, что ошибки здесь нет
if (err !== null) {
console.log("ERROR: " + err);
// выход из функции
return;
}
// установление счетчиков в целое
// значение, хранящееся в Redis, или к нулю,
// если оно не установлено
counts.awesome = parseInt(awesomeCount,10) || 0;
twitter.stream(
"statuses/filter",
{ track: ["awesome", "cool", "rad", "gnarly", "groovy"] },
function(stream) {
stream.on("data", function(tweet) {
if (tweet.text.indexOf("awesome") >= -1) {
// приращение ключевого значения клиента
client.incr("awesome");
counts.awesome = counts.awesome + 1;
}
});
}
);
});
module.exports = counts;

Вы заметили, что обратный вызов для get принимает два параметра: err
и awesomeCount? Параметр err представляет собой условие отсутствия ошибки и бу-
дет объектом error, если в запросе есть какая-либо проблема. Если же с запросом
все хорошо, он будет равен null. Обычно, выполняя запрос к хранилищу данных,
первое, что мы делаем в ответ, — проверяем на отсутствие ошибок и обрабатываем
запрос тем или иным образом. В противном случае просто выводим информацию
о появлении какой-либо проблемы. Но в случае вывода ваших приложений на
рынок ошибки нужно обрабатывать более корректно.
Затем вы видите, что необходимо каким-то образом обработать значение
awesomeCount. Поскольку Redis хранит все величины как строки, мы должны преоб-
разовать значение в целое число, чтобы выполнять с ним арифметические операции
в JavaScript. В данном случае мы использовали глобальную функцию parseInt,
которая извлекает числовое значение из строки, возвращенной Redis. Второй
Глава 7. Хранение данных

параметр, запрошенный parseInt, называется radix и означает, что нам нужны циф-
ры, составляющие число. Если же величина не число, parseInt возвращает значение
NaN, что означает — думаю, это понятно — Not a Number, то есть не число.
Помните, что || относится к оператору JavaScript «ИЛИ». Этот оператор вернет
первое значение в списке величин, которые не являются ложными, что значит — они
не равны false, 0 или NaN. Если ложные все величины, оператор вернет последнее.
В общем, данная строка кода переводится как «используй величину awesomeCount,
если она определена, или 0 в ином случае». Это позволяет нам обнулить код, когда
awesome не определена в отдельной строке кода. В этот момент вам стоит перерабо-
тать код с учетом всего сказанного ранее, но сначала лучше изучить еще одну ко-
манду Redis.

$PJHW’’$

Команда get прекрасно подходит для единичной пары «ключ — величина», но что
произойдет, если нам нужно запросить величину, связанную с несколькими клю-
чами? Это почти так же просто, если использовать функцию mget . Мы можем
преобразовать код следующим образом:
client.mget(["awesome", "cool"], function (err, results) {
if (err !== null) {
console.log("ERROR: " + err);
return;
}
counts.awesome = parseInt(results[0], 10) || 0;
counts.cool = parseInt(results[1], 10) || 0;
}

С помощью mget сможем преобразовать код для обработки всех слов, которые
нужно отслеживать.
Redis прекрасно справляется с хранением простых данных, которые могут пред-
ставляться в виде строк, включая объекты JSON. Но если нам нужен несколько
больший контроль над данными JSON, лучше использовать хранилище, разрабо-
танное специально для JSON. MongoDB — отличный пример такой технологии.

0RQJR’%
MongoDB (или для краткости Mongo) — база данных, которая позволяет хранить
данные на диске, но не в реляционном формате. Mongo является документоори-
ентированной базой данных, которая концептуально позволяет хранить данные
в виде коллекций в формате JSON (технически MongoDB хранит свои данные
в формате BSON, но в нашем случае мы вполне можем думать о них как о JSON).
Кроме того, она позволяет полноценно взаимодействовать с ними с помощью
JavaScript!
MongoDB

Mongo может быть использована для более сложных задач хранения данных,
например хранения учетных записей пользователей или комментариев к постам
в блогах. Или даже для хранения бинарных данных, например изображений! Mongo
отлично подходит для независимого хранения объектов задач Amazeriffic на нашем
сервере.

Œ0RQJR’%

Как и Redis, Mongo предлагает клиент с интерфейсом командной строки, который


позволяет напрямую взаимодействовать с хранилищем данных. Вы можете запу­
стить клиент Mongo, набрав mongo в командной строке на вашей гостевой машине:
vagrant $ mongo
MongoDB shell version: 2.4.7
connecting to: test
>

При первом запуске вы можете увидеть несколько начальных предупреждений, но это


совершенно нормально.

Одно из основных различий между Redis и Mongo состоит в том, что мы можем
взаимодействовать с Mongo с помощью JavaScript! Например, здесь мы создаем
переменную под названием card (карта) и сохраняем в ней объект:
> var card = { "rank":"туз", "suit":"треф" };
> card
{ "rank" : "туз", "suit" : "треф" }
>
Аналогичным образом можем создавать массивы и управлять ими. Отмечу, что
в этом примере мы не завершаем операторы в конце каждой строки. После нажатия
Enter Mongo выдает три точки, давая знать, что предыдущий оператор не завершен.
Mongo автоматически выполнит первый оператор JavaScript:
> var clubs = [];
> ["двойка", "тройка", "четверка", "пятерка"].forEach(
... function (rank) {
... cards.push( { "rank":rank, "suit":"треф" } )
... });
> clubs
[
{
"rank" : "двойка",
"suit" : "треф"
},
{
Глава 7. Хранение данных

"rank" : "тройка",
"suit" : "треф"
},
{
"rank" : "четверка",
"suit" : "треф"
},
{
"rank" : "пятерка",
"suit" : "треф"
}
]

Другими словами, клиент Mongo с интерфейсом командной строки работает


почти как консоль JavaScript в Chrome. Но это сходство исчезнет, как только мы
примемся работать с хранением данных. Mongo организует данные как документы,
которые мы можем рассматривать как объекты JSON. Коллекции документов хра-
нятся в базах данных. Мы можем увидеть базы данных MongoDB с помощью ко-
манды show dbs:
> show dbs
local 0.03125GB

Локальная база данных всегда находится здесь. Мы можем переключиться на


другую базу данных с помощью команды use:
> use test
switched to db test

(ответ: переключились на базу данных test).


Выбрав для использования нужную базу данных, мы можем получить доступ
к ней через объект db. Мы можем сохранять объекты в коллекции, вызвав функцию
save для коллекции. Если коллекции еще не существует, Mongo ее создаст. Здесь
мы сохраняем карту, созданную ранее, в нашу коллекцию:
> show collections;
> db.cards.save(card);
> show collections;
cards
system.indexes

Вы видите, что коллекция cards не существовала перед тем, как в нее сохранили
объект. Мы можем использовать для коллекций функцию find, не требующую
аргументов, чтобы увидеть, какие документы там хранятся:
> db.cards.find();
{ "_id" : ObjectId("526ddeea7ba2be67c95558d8"),"rank":"туз","suit":"треф" }

В дополнение к свойствам rank (номинал) и suit (масть) карта имеет также _id,
связанный с ней. В большинстве случаев каждому документу в коллекции MongoDB
присваивается идентификационный номер.
MongoDB

Мы можем не только сохранить единичный элемент, но и добавить несколько


документов в коллекцию, используя один вызов save. Проделаем это на примере
массива clubs (трефы), созданного ранее:
> db.cards.save(clubs);
> db.cards.find();
{ "_id" : ObjectId("526ddeea7ba2be67c95558d8"),"rank":"туз","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558d9"),"rank":"двойка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558da"),"rank":"тройка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558db"),"rank":"четверка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558dc"),"rank":"пятерка","suit":"треф" }

Мы можем также добавить в базу данных больше объектов:


>hearts = [];
> ["двойка", "тройка", "четверка", "пятерка"].
... forEach(function (rank)
... { hearts.push( { "rank":rank, "suit":"треф" } )
... });
> db.cards.save(hearts);
> db.cards.find();
{ "_id" : ObjectId("526ddeea7ba2be67c95558d8"),"rank":"туз","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558d9"),"rank":"двойка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558da"),"rank":"тройка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558db"),"rank":"четверка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558dc"),"rank":"пятерка","suit":"треф" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558de"),"rank":"двойка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558df"),"rank":"тройка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558e0"),"rank":"четверка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558e1"),"rank":"пятерка","suit":"червей" }

После того как в коллекции появится много разнообразных документов, мы можем


получить их, создавая запросы из объектов JSON, которые представляют собой
свойства нужного нам документа. Например, мы можем запросить все документы
с картами номиналом двойка и сохранить их в переменной под названием twos:
> var twos = db.cards.find({"rank":"двойка"});
> twos
{ "_id" : ObjectId("526ddeea7ba2be67c95558d9"),"rank":"двойка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558de"),"rank":"двойка","suit":"треф" }

Или выбрать все тузы:


> var aces = db.cards.find({"rank":"туз"});
> aces
{ "_id" : ObjectId("526ddeea7ba2be67c95558d8"),"rank":"туз","suit":"треф" }

Можем также удалить элементы из коллекции, вызвав метод remove и отправив


его в запросе:
> db.cards.remove({"rank":"двойка"});
> db.cards.find();
{ "_id" : ObjectId("526ddeea7ba2be67c95558da"),"rank":"тройка","suit":"треф" }
Глава 7. Хранение данных

{ "_id" : ObjectId("526ddeea7ba2be67c95558db"),"rank":"четверка","suit":"треф" }
{ "_id" : ObjectId("526ddeea7ba2be67c95558dc"),"rank":"пятерка","suit":"треф" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558df"),"rank":"тройка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558e0"),"rank":"четверка","suit":"червей" }
{ "_id" : ObjectId("526ddf0f7ba2be67c95558e1"),"rank":"пятерка","suit":"червей" }
Или же удалить все документы из коллекции, вызвав remove с пустым запро-
сом:
> db.cards.remove();
> db.cards.find();
>
Аналогично Redis MongoDB представляет интерактивный учебник (рис. 7.2),
который вы можете опробовать в своем браузере. Я рекомендую вам самостоятель-
но поработать с этим учебником, чтобы узнать немного больше о функционально­сти
MongoDB, а также доступных типах запросов.

Рис. 7.2. Домашняя страница интерактивного учебника MongoDB

В конечном итоге, однако, мы не будем использовать особенно много команд


Mongo для Node.js по умолчанию. Вместо этого будем моделировать данные как
объекты с помощью модуля Node.js, называемого Mongoose.

#0RQJRRVH
Mongoose — это модуль Node.js, который служит для двух основных целей. Прежде
всего он работает как клиент для MongoDB аналогично тому, как модуль node-redis
MongoDB

работает в качестве клиента Redis. Но кроме этого, Mongoose работает как инструмент
для моделирования данных, позволяющий представлять документы как объекты в на-
ших программах. В этом разделе мы изучим основы моделирования данных и ис-
пользуем Mongoose для создания модели данных для задач Amazeriffic.

Модель данных — это всего лишь объектное представление коллекции документов


в хранилище данных. Кроме указания полей, которые присутствуют в каждом
документе коллекции, она добавляет для базы данных MongoDB операции напо-
добие save или find для соответствующих объектов.
В Mongoose модель данных содержит схему, которая описывает структуру всех
объектов такого типа. Например, предположим, что мы хотим создать модель дан-
ных для коллекции игральных карт. Начнем с указания схемы для карты — в част­
ности, непосредственно укажем, что каждая карта обладает номиналом и мастью.
В нашем файле JavaScript это будет выглядеть примерно так:
var CardSchema = mongoose.Schema({
"rank" : String,
"suit" : String
});

Имея схему, построить модели очень просто. По договоренности мы использу-


ем заглавную букву для объектов моделей данных:
var Card = mongoose.model("Card", CardSchema);

Схемы могут быть более сложными. Например, мы можем построить схему для
записей в блоге, которая будет содержать даты и комментарии. В этом примере
атрибут comments (комментарии) будет представлять собой массив строк вместо
единичной строки:
var BlogPostSchema = mongoose.Schema({
title: String,
body : String,
date : Date,
comments : [ String ]
});

Как же модель может нам помочь? Имея модель, мы можем создать объект соот-
ветствующего ей типа очень просто, используя оператор JavaScript new. Например,
такая строка кода создает туз пик и сохраняет его в переменной под названием с1:
var c1 = new Card({"rank":"туз", "suit":"пик"})

Отлично, но не проще ли проделать то же самое с помощью вот такой строки


кода:
var c2 = { "rank":"туз", "suit":"пик" };

Разница состоит в том, что объект Mongoose позволяет нам взаимодействовать


с базой данных с помощью некоторых встроенных функций:
Глава 7. Хранение данных

// сохраняем эту карту в хранилище данных


c1.save(function (err) {
if (err !== null) {
// объект не был сохранен
console.log(err);
} else {
console.log("Объект не был сохранен!");
}
});

Мы также можем взаимодействовать с моделью напрямую, получая объекты из


базы данных с помощью функции find для данной части модели. Аналогично функ­
ции find в интерактивном клиенте MongoDB эта функция принимает произвольные
запросы MongoDB. Разница состоит в том, что она ограничивает саму себя типами,
которые определены моделью:
Card.find({}, function (err, cards) {
if (err !== null) {
console.log("ОШИБКА: " + err);
// возвращаемся из функции
return;
}
// если мы попали сюда, значит, ошибки нет
Cards.forEach(function (card) {
// это введет все карты в базу данных
console.log (card.rank + " of " + card.suit);
});
});

Мы можем также находить какие-либо элементы (через их идентификаторы


или другие запросы), обновлять их и снова сохранять. Например, допустим, что
нужно изменить масть всех карт червей на пик:
Card.find({"suit" : " червей"}, function (err, cards) {
cards.forEach(function (card) {
// обновляем карты пик
card.suit = "пик";
// сохраняем обновленную карту
card.save(function (err) {
if (err) {
// если объект не был сохранен
console.log(err);
}
});
});
});

И наконец, мы можем удалять элементы из базы данных, вызывая функцию


remove для модели:
Card.remove({ "rank":"туз", "suit":"треф" }, function (err) {
if (err !== null) {
MongoDB

// если объект не был успешно удален


console.log(err);
}
});

К этому моменту мы рассмотрели примеры четырех основных операций над


данными с помощью Mongoose: создание, чтение, обновление и удаление. Но мы
все еще не видели работу Mongoose в веб-приложении! Рассмотрим ее сейчас.

’$PD]HULI¿F
Для начала скопируем текущую версию кода Amazeriffic (и клиентскую и серверную
часть) в папку Chapter7. Нам понадобится файл package.json, где присутствует вза-
имосвязь с Mongoose:
{
"name": "amazeriffic",
"description": "The best to-do list app in the history of the world",
"version": "0.0.1",
"dependencies": {
"mongoose": "3.6.x"
}
}

Сделав это, запустим npm install для установки модуля mongoose. Затем можем
добавить код в server.js для импорта модуля:
var express = require("express"),
http = require("http"),
// импортируем библиотеку mongoose
mongoose = require("mongoose"),
app = express();
app.use(express.static(__dirname + "/client"));
app.use(express.urlencoded());
// подключаемся к хранилищу данных Amazeriffic в Mongo
mongoose.connect('mongodb://localhost/amazeriffic');

Затем определим схему и, следовательно, модель для списка задач в серверном


коде:
// Это модель Mongoose для задач
var ToDoSchema = mongoose.Schema({
description: String,
tags: [ String ]
});
var ToDo = mongoose.model("ToDo", ToDoSchema);
// начинаем слушать запросы
http.createServer(app).listen(3000);

Мы можем обновить маршрут для получения задач из базы данных и возвра-


щения туда:
Глава 7. Хранение данных

app.get("/todos.json", function (req, res) {


ToDo.find({}, function (err, toDos) {
// не забудьте о проверке на ошибки!
res.json(toDos);
});
});
И наконец, нужно обновить маршрут post для добавления элементов в базу
данных. Это немного интереснее, потому что для сохранения совместимости с кли-
ентом, модифицированным в главе 2, придется возвращать весь список задач:
app.post("/todos", function (req, res) {
console.log(req.body);
var newToDo = new ToDo({"description":req.body.description,
"tags":req.body.tags});
newToDo.save(function (err, result) {
if (err !== null) {
console.log(err);
res.send("ERROR");
} else {
// клиент ожидает, что будут возвращены все задачи,
// поэтому для сохранения совместимости сделаем дополнительный запрос
ToDo.find({}, function (err, result) {
if (err !== null) {
// элемент не был сохранен
res.send("ERROR");
}
res.json(result);
});
}
});
});
Сейчас мы можем запустить сервер и подключиться к нему через браузер. Помни-
те, что мы еще не добавили в нашу коллекцию никаких задач, поэтому следует добавить
несколько штук, прежде чем мы увидим в приложении какие-либо элементы.

В этой главе мы научились хранить данные независимо от приложения. Мы рас-


смотрели два решения для хранения данных, которые являются примерами баз
данных формата не-SQL, — Redis и MongoDB.
Redis — хранилище типа «ключ — значение». Он предоставляет быструю и гибкую
технологию для хранения простых данных. Модуль node-redis позволяет взаимо-
действовать с Redis почти точно так же, как мы делали это из командной строки.
MongoDB — более надежное решение для хранения данных, которое организу-
ет базы данных в коллекции. Как пользователи базы данных, мы можем думать о ней
как о хранилище данных в виде объектов JavaScript. Кроме того, интерфейс команд­
ной строки для Mongo может быть использован как базовый интерпретатор
JavaScript, позволяющий нам взаимодействовать с нашими коллекциями.
Больше теории и практики

Mongoose — это модуль Node.js, который позволяет нам взаимодействовать


с MongoDB, а кроме того, еще и создавать модели данных. Модели данных не только
указывают, как должны выглядеть все элементы какой-либо коллекции, но и позво-
ляют нам взаимодействовать с базой данных через объекты в программе.

Ø$  
$3,
Если вы проработали все практические разделы в предыдущих главах, то построи­
ли покерное API, которое принимает покерную раздачу и возвращает ее комбина-
цию (то есть пару, фулл-хауз и др.). Было бы интересно добавить в ваш сервер
Express код, который хранит результат для каждой раздачи и ее тип в Redis или
Mongo. Другими словами, сделайте так, чтобы приложение продолжало отслежи-
вать каждую корректную раздачу, размещенную в API, вместе с результатом. Не сле-
дует отслеживать элементы с ошибками.
Если вы сделаете это, то можете настроить маршрут get, который будет возвра-
щать объект JSON, включающий в себя все сохраненные раздачи. Вы можете даже
пожелать настроить get так, чтобы он возвращал только пять последних раздач.
Если же хотите сделать этот пример по-настоящему интересным, попробуйте
найти библиотеку с изображениями игральных карт и заставьте ваш клиент пока-
зывать изображения карт в раздаче вместо объектов JSON или текстового описания.
Чтобы сделать это, нужно будет создать подпапку images в клиентской части ваше-
го приложения и хранить изображения там.

#
Базы данных — очень обширная тема, материала которой хватит на несколько книг.
Если вы хотите узнать больше о разработке веб-приложений и о программировании
в целом, обязательно попытайтесь изучить основы реляционных баз данных и SQL.
Coursera — прекрасная возможность пройти бесплатные онлайн-классы по темам
баз данных среди прочего. Например, там есть курс для самостоятельного обучения,
составленный Дженнифер Видом из Стэнфордского университета!
Если же вы предпочитаете книги, Дженнифер Видом является соавтором (вме­
сте с Гектором Гарсия-Молина и Джеффри Уллманом) книги «Системы баз данных:
Полная книга» (Database Systems: The Complete, Prentice Hall, 2008). Она представ-
ляет довольно-таки академический подход к теме, но я думаю, что это великолеп-
ное, легко читаемое произведение.
Еще одна прекрасная книга, освещающая различные типы баз данных (включая
не-SQL), — «Семь баз данных за семь недель» Эрика Рэдмонда и Джима Уилсона
(Seven Databases in Seven Weeks, Pragmatic Bookshelf, 2012). Кроме Mongo и Redis,
в книге описаны такие популярные варианты, как PostgreSQL и Riak. Авторы
проделали большую работу по описанию плюсов и минусов каждой базы данных,
а также разнообразных сценариев их использования.
К этому моменту вы уже имеете представление о создании веб-приложения с ис-
пользованием клиентских и серверных технологий, но приложение, очевидно, не
будет широко использоваться, пока существует только на вашем компьютере. Сле-
дующий фрагмент головоломки — размещение приложения в Сети и запуск его
в Интернете.
В прошлом для этого требовался огромный объем работы: нужно было оплатить
место на сервере с фиксированным IP-адресом, установить и настроить необхо-
димое ПО, оплатить доменное имя, а затем связать его со своим приложением.
К счастью, времена изменились и в наши дни существует категория хостинговых
сервисов под названием «платформа-сервис» (Platforms-as-a-Service — PaaS),
которые могут выполнить за нас всю рутинную работу.
Вы, может быть, слышали термин «облачные вычисления»? Эта концепция
основана на том, что низкоуровневые детали содержания программного обес-
печения и вычислений могут и должны быть перенесены на локальные компью-
теры и в Интернет. С PaaS нам не нужно беспокоиться о любых деталях обслу-
живания и настройки веб-сервера, так что мы можем сфокусироваться только
на обеспечении корректной работы своего веб-приложения. Другими словами,
PaaS — это тип технологии, обеспечивающий работу облачной вычислительной
модели.
В этой главе мы изучим, как загрузить свои веб-приложения на открытый ресурс
PaaS, который называется Cloud Foundry. Хотя мы сконцентрируемся на Cloud
Foundry, большинство изученных нами концепций будут справедливы и для других
сервисов PaaS, например Heroku или Nodejitsu.

&ORXG)RXQGU\
Cloud Foundry — это открытый ресурс PaaS, изначально разработанный VMWare.
Вы можете прочитать больше об этом сервисе на его домашней странице, располо-
женной по адресу http://cloudfoundry.com (рис. 8.1). Ресурс предлагает бесплатное
использование на 60 дней, в течение которых вы можете попробовать загрузить
туда некоторые из примеров-приложений из этой книги.
Подготовка приложений к развертыванию в Сети

Рис. 8.1. Домашняя страница Cloud Foundry1

ł’
Чтобы начать, вам нужно создать учетную запись на http://www.cloudfoundry.com. Вы
можете получить 60 дней бесплатного использования, щелкнув на ссылке в верхнем
правом углу и набрав свой адрес электронной почты. В ответ получите информацию
о том, как настроить учетную запись.

 #&
ø 
Чтобы развернуть приложение, вам понадобится инструмент, называемый cf. Ин-
сталляция его на локальную машину требует предварительной установки языка
Ruby и пакет-менеджера RubyGems. Я включил приложение cf в проект node-dev-
bootstrap, так что вы можете начать развертывание прямо с гостевой машины без
всяких дополнительных установок!
Чтобы начать, создайте папку Chapter8 клонированием проекта node-dev-bootstrap,
как вы уже делали в предыдущих двух главах. Покончив с этим, запустите гостевую
машину и подключитесь к ней по SSH.

1
На момент перевода с адреса, указанного в предыдущем абзаце, идет перенаправление
на тот, что вы видите на скриншоте в URL. Однако этот сервис предлагает примерно те
же функции, что и Cloud Foundry, поэтому вы можете и далее пользоваться инструкциями
из книги. — Примеч. пер.
Глава 8. Платформа

Начнем с развертывания базовой программы server.js, которая появилась у нас


вместе с node-dev-bootstrap, но перед этим нам придется проделать еще две вещи.
Вначале понадобится добавить файл package.json, так как Cloud Foundry ожидает
найти его во всех приложениях, которые мы будем разворачивать. Поскольку наше
серверное приложение по умолчанию не зависит от каких-либо внешних модулей
Node.js, файл будет очень коротким:
{
"name":"sp_example",
"description": "Мое первое приложение Cloud Foundry!"
}

Затем нужно сделать небольшое изменение в файле по умолчанию server.js,


который поставляется вместе с проектом node-dev-bootstrap. Это изменение затра-
гивает номер порта, по которому производится слушание сервера: по техническим
причинам Cloud Foundry сам должен назначить нам номер порта для слушания, но
мы не будем знать этот номер до запуска кода. К счастью, Cloud Foundry достав-
ляет номер порта через переменную окружения, которая называется PORT. Мы можем
получить к ней доступ через переменную process.env.PORT в программе Node.js:
var http = require("http"),
// если переменная окружения PORT настроена,
// слушать по ней, если же нет, слушать
// по порту 3000
port = process.env.PORT || 3000;
var server = http.createServer(function (req, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Привет от Cloud Foundry!");
});
server.listen(port);
console.log("Сервер слушает по порту " + port);

Здесь мы снова встречаем идиому ||, с которой уже столкнулись в главе 7.


А вообще этот код делает следующее: если process.env.port определена, использо-
вать ее, если нет, слушать по порту 3000. Это позволяет настроить номер порта так,
что приложение будет корректно работать, будучи как размещенным на гостевой
машине, так и развернутым на Cloud Foundry.

ł#’
К этому моменту мы уже можем запустить виртуальную машину и подключиться
к ней через браузер, как и раньше. А сейчас, создав файл package.json и настроив
приложение на слушание по корректному порту, мы также готовы загрузить его на
Cloud Foundry.
Как я упоминал ранее, это потребует использования программы cf, которая уже
установлена на гостевой машине. Чтобы начать, перейдите на гостевой машине
в папку, где находится файл server.js.
Развертывание приложения

API платформы Cloud Foundry располагается на api.run.pivotal.io. Поэтому


начнем с указания cf, где находится целевая платформа Cloud Foundr, с помощью
субкоманды target:
vagrant $ cf target api.run.pivotal.io
Setting target to https://api.run.pivotal.io... OK
Затем нужно авторизоваться с помощью команды login и ввести данные для
входа в учетную запись Cloud Foundry. После авторизации cf спросит, какое про-
странство для развертывания мы хотим использовать. Я обычно использую про-
странство development, когда экспериментирую:
vagrant $ cf login
target: https://api.run.pivotal.io
Email> me@semmy.me
Password> ************
Authenticating... OK
1: development
2: production
3: staging
Space> 1
Switching to space development... OK
Если все сделано правильно, следующий шаг — отправка приложения на Cloud
Foundry! Как это сделать? Это так же просто, как использовать команду push. Вме­
сте с субкомандой push нужно включить команду для запуска приложения:
vagrant $ cf push --command "node server.js"
Субкоманда push отправляет нас к диалогу с Cloud Foundry. Нужно будет отве-
тить на несколько вопросов о приложении и об окружении, которое мы будем ис-
пользовать. Вот диалог, который я провел с Cloud Foundry, — ваш, скорее всего,
будет очень похожим:
Name> sp_example
Instances> 1
1: 128M
2: 256M
3: 512M
4: 1G
Memory Limit> 256M
Creating sp_example... OK
1: sp_example
2: none
Subdomain> sp_example
1: cfapps.io
2: none
Domain> cfapps.io
Creating route sp_example.cfapps.io... OK
Binding sp_example.cfapps.io to sp_example... OK
Create services for application?> n
Save configuration?> n
Uploading sp_example... OK
Глава 8. Платформа

Preparing to start sp_example... OK


-----> Downloaded app package (4.0K)
-----> Resolving engine versions
WARNING: No version of Node.js specified in package.json, see:
https://devcenter.heroku.com/articles/nodejs-versions
Using Node.js version: 0.10.21
Using npm version: 1.2.30
-----> Fetching Node.js binaries
-----> Vendoring node into slug
-----> Installing dependencies with npm
npm WARN package.json sp_example@ No repository field.
npm WARN package.json sp_example@ No readme data.
Dependencies installed
-----> Building runtime environment
-----> Uploading droplet (15M)
Checking status of app 'sp_example'...
1 of 1 instances running (1 running)
Push successful! App 'sp_example' available at sp_example.cfapps.io

Имя вашего приложения должно быть уникальным среди всех имен в этом домене. Это
значит, что, если вы выберете имя наподобие example, скорее всего, получите сообще-
ние о невозможности развертывания. Я стараюсь избегать этой проблемы, добавляя мои
инициалы и знак подчеркивания в начале имени. Это не всегда работает, поэтому по­
пробуйте найти собственные способы создания уникальных имен для приложений.

Если все прошло хорошо, ваше приложение сейчас запущено в Веб! Можете
убедиться в этом, открыв браузер и перейдя по URL, который выдала вам cf (в моем
примере это http://sp_example.cfapps.io). Сделав это, вы должны увидеть ответ от
вашего приложения.


Сейчас, когда ваше приложение загружено и работает, можете использовать другие
субкоманды cf для получения информации о статусе приложений. Например, мо-
жете использовать субкоманду apps для получения списка ваших приложений,
находящихся на Cloud Foundry, и их статусов:
vagrant $ cf apps
name status usage url
sp_example running 1 x 256M sp_example.cfapps.io
Одна из главных проблем запуска приложений на PaaS — то, что вы не можете
видеть в консоли результаты работы операторов console.log так же просто, как это
было при запуске приложений локально. Это может быть серьезной проблемой,
если ваша программа сбоит и надо понять почему. К счастью, Cloud Foundry пре-
доставляет субкоманду logs, которую вы можете использовать для запущенных
приложений, чтобы просмотреть программные логи:
Получение информации о приложениях

vagrant $ cf logs sp_example


Getting logs for sp_example #0... OK
Reading logs/env.log... OK
TMPDIR=/home/vcap/tmp
VCAP_APP_PORT=61749
USER=vcap
VCAP_APPLICATION= { ... }
PATH=/home/vcap/app/bin:/home/vcap/app/node_modules/.bin:/bin:/usr/bin
PWD=/home/vcap
VCAP_SERVICES={}
SHLVL=1
HOME=/home/vcap/app
PORT=61749
VCAP_APP_HOST=0.0.0.0
MEMORY_LIMIT=256m
_=/usr/bin/env
Reading logs/staging_task.log... OK
-----> Downloaded app package (4.0K)
-----> Resolving engine versions
WARNING: No version of Node.js specified in package.json, see:
https://devcenter.heroku.com/articles/nodejs-versions
Using Node.js version: 0.10.21
Using npm version: 1.2.30
-----> Fetching Node.js binaries
-----> Vendoring node into slug
-----> Installing dependencies with npm
npm WARN package.json sp_example@ No repository field.
npm WARN package.json sp_example@ No readme data.
Dependencies installed
-----> Building runtime environment
Reading logs/stderr.log... OK
Reading logs/stdout.log... OK
Server listening on port 61749

Вы видите, что Cloud Foundry выводит содержимое четырех сохраненных логов.


Первый — env.log, где находятся все переменные окружения, к которым вы може-
те получить доступ через переменную process.env в программе. Второй — staging_
task.log, в который записывается все, что происходит во время первого запуска
программы (вы увидите, что там записано то же самое, что было выведено на экран,
когда вы в первый раз запустили cf push). Последние два — stderr.log и stdout.log.
Вы увидите, что stdout.log включает в себя оператор console.log, который вы ис-
пользовали в своей программе. Если вместо него был указан console.err, сообщение
появится в stderr.log.


Вы легко можете послать новейшую версию приложения в Cloud Foundry, развер-
нув его заново. Модифицируем server.json так, чтобы он возвращал несколько
больше информации:
Глава 8. Платформа

var http = require("http"),


port = process.env.PORT || 3000;
var server = http.createServer(function (req, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("Сервер слушает по порту " + port);
res.end("Привет от Cloud Foundry!");
});
server.listen(port);
console.log("Сервер слушает по порту " + port);
Как только вы внесете это изменение, можете запустить субкоманду push
заново вместе с именем приложения, которое хотите обновить. Соответству­
ющие изменения будут внесены без необходимости заново отвечать на все
вопросы:
vagrant $ cf push sp_example
Save configuration?> n
Uploading sp_example... OK
Stopping sp_example... OK
Preparing to start sp_example... OK
-----> Downloaded app package (4.0K)
-----> Downloaded app buildpack cache (4.0K)
-----> Resolving engine versions
WARNING: No version of Node.js specified in package.json, see:
https://devcenter.heroku.com/articles/nodejs-versions
Using Node.js version: 0.10.21
Using npm version: 1.2.30
-----> Fetching Node.js binaries
-----> Vendoring node into slug
-----> Installing dependencies with npm
npm WARN package.json sp_example@ No repository field.
npm WARN package.json sp_example@ No readme data.
Dependencies installed
-----> Building runtime environment
-----> Uploading droplet (15M)
Checking status of app 'sp_example'...
1 of 1 instances running (1 running)
Push successful! App 'sp_example' available at sp_example.cfapps.io

ß
&ORXG)RXQGU\
Иногда, оказывается, нужно удалить приложения из Cloud Foundry, особенно если
мы просто экспериментируем. Чтобы сделать это, можно использовать субкоман-
ду delete:
vagrant $ cf delete sp_example
Really delete sp_example?> y
Deleting sp_example... OK
Получение информации о приложениях

ΠSDFNDJHMVRQ
В предыдущем примере наши приложения имели внешние взаимозависимости
наподобие модулей express, redis, mongoose и ntwitter. Использование базовых мо-
дулей, которые не подключаются к внешним сервисам (например, Express или
Twitter), — весьма незамысловатое решение. Поскольку обычно мы не отправляем
в облачное хранилище каталог node_modules, нужно просто удостовериться в том,
что все взаимозависимости перечислены в файле package.json.
Например, вспомните одно из наших первых приложений Express. После вы-
полнения небольших модификаций оно начинает слушание по корректному порту
Cloud Foundry, что выглядит следующим образом:
var express = require("express"),
http = require("http"),
app = express(),
port = process.env.PORT || 3000;;
http.createServer(app).listen(port);
console.log("Express is listening on port " + port);
app.get("/hello", function (req, res) {
res.send("Hello, World!");
});
app.get("/goodbye", function (req, res) {
res.send("Goodbye World!");
});
Нужно будет включить взаимозависимость с модулем Express в файл package.json,
что мы и сделали в предыдущем примере с package.json:
{
"name": "sp_express",
"description": "a sample Express app",
"dependencies": {
"express": "3.4.x"
}
}
Как видите, я указал, что приложение зависит от модуля Express, в частности
от всех версий, номер которых начинается с 3.4 (х — подстановочный символ).
Таким образом, CLoud Foundry понимает, какую версию нужно установить, чтобы
приложение работало корректно. Поскольку взаимозависимости включены в файл
package.json, мы можем отправить его в Cloud Foundry с помощью той же самой
команды, что и раньше:
vagrant $ ~/app$ cf push --command "node server.js"
Name> sp_expressexample
Сделав это, мы можем зайти на http://sp_expressexample.cfapps.io/hello или http://
sp_expressexample.cfapps.io/goodbye, чтобы увидеть ответ приложения!
Но заставить работать наши приложения Twitter или Amazeriffic несколько
сложнее, так как они зависят от других хранилищ данных — в частности, Redis или
MongoDB. Это значит, что нужно создать сервисы, а затем заставить приложения
их использовать.
Глава 8. Платформа

’5HGLV&
Когда мы запускаем приложение на виртуальной машине, такие сервисы, как Redis
или MongoDB, работают локально. Но это будет не так, если мы запустим прило-
жение из PaaS. Иногда сервисы работают на том же самом хосте, но в некоторых
случаях могут запускаться и на другом.
В любом случае начать следует с настройки сервиса, который требуется запу­
стить при взаимодействии с cf. В этой секции настроим Redis в Cloud Foundry, а за-
тем подключим к нему счетчик твитов.
Начнем с копирования приложения Twitter из главы 7 в папку Chapter8. Убеди-
тесь в том, что файл Package.json существует и включает взаимозависимости ntwitter
и redis. Мой выглядит вот так:
{
"name": "tweet_counter",
"description": "tweet counter example for learning web app development",
"dependencies": {
"ntwitter":"0.5.x",
"redis":"0.9.x"
}
}

Нужно также обновить файл server.js так, чтобы сервер слушал по порту, ука-
занному в process.env.PORT. Сделав это, мы можем попробовать развернуть прило-
жение! Я так и сделаю (забегая вперед, скажу, что попытка будет неудачной, но
даст возможность развернуть сервис Redis):
vagrant $ cf push --command "node server.js"
Name> sp_tweetcounter
Instances> 1
1: 128M
2: 256M
3: 512M
4: 1G
Memory Limit> 256M
Creating sp_tweetcounter... OK
1: sp_tweetcounter
2: none
Subdomain> sp_tweetcounter
1: cfapps.io
2: none
Domain> cfapps.io
Binding sp_tweetcounter.cfapps.io to sp_tweetcounter... OK
Create services for application?> y
1: blazemeter n/a, via blazemeter
2: cleardb n/a, via cleardb
3: cloudamqp n/a, via cloudamqp
4: elephantsql n/a, via elephantsql
5: loadimpact n/a, via loadimpact
Привязка Redis к приложению

6: mongolab n/a, via mongolab


7: newrelic n/a, via newrelic
8: rediscloud n/a, via garantiadata
9: sendgrid n/a, via sendgrid
10: treasuredata n/a, via treasuredata
11: user-provided , via

Обратите внимание: мы сказали Cloud Foundry, что хотим создать сервис для
приложения:
What kind?> 8
Name?> rediscloud-dfc38
1: 20mb: Lifetime Free Tier
Which plan?> 1
Creating service rediscloud-dfc38... OK
Binding rediscloud-dfc38 to sp_tweetcounter... OK
Create another service?> n
Bind other services to application?> n
Save configuration?> n

Закончив диалог, вы обнаружите, что попытка развертывания не удалась. Так


случилось потому, что мы еще не указали приложению, как подключиться к вне-
шнему сервису Redis. Логи должны дать нам подсказку об этом. И в самом деле,
запустив cf logs sp_tweetcounter, я увижу что-то похожее в моем logs/stderr.log:
Reading logs/stderr.log... OK
events.js:72
throw er; // Unhandled 'error' event
^
Error: Redis connection to 127.0.0.1:6379 failed — connect ECONNREFUSED
at RedisClient.on_error (/home/vcap/app/node_modules/redis/index.js:189:24)
at Socket.<anonymous> (/home/vcap/app/node_modules/redis/index.js:95:14)
at Socket.EventEmitter.emit (events.js:95:17)
at net.js:441:14
at process._tickCallback (node.js:415:13)

В наших приложениях мы не отправляем никаких аргументов Redis, поэтому


она пытается подключиться к локальному серверу (127.0.0.1), используя порт
по умолчанию (6379). Нужно дать указание подключаться к облачному сервису
Redis по хосту и порту, которые предоставляет Cloud Foundry. Где же найти эту
информацию? Как и номер порта, все о связях сервисов указано в переменной
process.env!
Если вы уже ввели cf logs sp tweetcounter, то можете прокрутить окно вниз,
чтобы просмотреть переменные окружения и содержание переменной VCAP_SERVICES.
В логах она представлена в виде одной длинной строки JSON, поэтому здесь я при-
вел ее к более удобочитаемому виду:
VCAP_SERVICES = {
"rediscloud-n/a": [{
"name": "rediscloud-dfc38",
"label": "rediscloud-n/a",
Глава 8. Платформа

"tags": ["redis", "key-value"],


"plan": "20mb",
"credentials": {
"port": "18496",
"hostname": "pub-redis-18496.us-east-1-4.2.ec2.garantiadata.com",
"password": "REDACTED"
}
}]
}
Здесь находится вся информация, необходимая для подключения к удаленному
сервису Redis, включая URL, порт и пароль. Можно было бы напрямую прописать
данные для входа в Redis в нашей программе, но, скорее всего, лучше будет сделать
это программным способом, аналогично тому как мы установили номер порта.

Есть небольшое затруднение — переменная окружения VCAP_SERVICES хранится в виде


строки, поэтому нужно использовать команду JSON.parse так же, как в главе 5, чтобы
конвертировать переменную в объект, с которым мы можем работать как с любым дру-
гим объектом JavaScript.

Если говорить коротко, мы можем заставить Redis работать, модифицировав


первую часть модуля tweet_counter.js следующим образом:
var ntwitter = require("ntwitter"),
redis = require("redis"), // требуется модуль Redis
credentials = require("./credentials.json"),
redisClient,
counts = {},
twitter,
services,
redisCredentials;
// создание клиента Twitter
twitter = ntwitter(credentials);
// настройка сервисов, если существует переменная окружения
if (process.env.VCAP_SERVICES) {
// анализ строки JSON
services = JSON.parse(process.env.VCAP_SERVICES);
redisCredentials = services["rediscloud-n/a"][0].credentials;
} else {
// в ином случае мы используем значения по умолчанию
redisCredentials = {
"hostname": "127.0.0.1",
"port": "6379",
"password": null
};
}
// создаем клиент для подключения к Redis
client = redis.createClient(redisCredentials.port, redisCredentials.hostname);
// аутентификация
client.auth(redisCredentials.password);
Привязка MongoDB к приложению

В этом фрагменте кода мы проверяем, существует ли переменная окружения


VCAP_SERVICES, и, если да, анализируем связанную с ней строку, чтобы сгенерировать
объект JavaScript из представления JSON. Затем отправляем данные для входа,
связанные со свойством rediscloud-n/a в объекте services. Сам по себе этот объект
является массивом (так как наше приложение должно быть связано с несколькими
экземплярами Redis), поэтому нужно получить первый элемент массива.
Если переменная окружения VCAP_SERVICES не определена, мы настраиваем объ-
ект redisCredentials, куда заложены значения по умолчанию. После этого подклю-
чаемся к Redis, указав порт и имя хоста, а затем отправляем пароль. Если мы под-
ключены к Redis локально на виртуальной машине, в качестве пароля отправляется
null, так как аутентификации в данном случае не требуется.
Если предыдущая попытка развернуть приложение не удалась, мы можем пред-
принять новую. В этот раз я использую предварительно заданное имя. Если забуду
его, то всегда могу использовать команду cf apps, чтобы увидеть список моих при-
ложений:
vagrant $ cf apps
Getting applications in development... OK
name status usage url
sp_example running 1 x 256M sp_example.cfapps.io
sp_express running 1 x 256M sp_express.cfapps.io
sp_tweetcounter running 1 x 256M sp_tweetcounter.cfapps.io
vagrant $ cf push sp_tweetcounter
Если все настроено правильно, то вы увидите, что приложение успешно раз-
вернуто, и можете получить доступ к нему через URL, который получите от Cloud
Foundry.

’0RQJR’%&
Привязка MongoDB к приложению и ее запуск в Cloud Foundry почти так же про-
сты, как те же операции с Redis. Начнем с копирования приложения Amazeriffic из
главы 7 в текущую папку. Придется сделать несколько небольших изменений по
уже знакомому образцу.
Итак, начнем с указания слушать по порту process.env.PORT, если эта величина
существует. Код для этого идентичен приведенному в примерах в предыдущем
разделе.
После этого нужно получить данные входа из process.env.VCAP_SERVICES. Код
будет очень похожим на код для Redis. Основное отличие состоит в том, что данные
для входа в MongoDB содержатся в единичной строке — uri:
// не забудьте объявить переменную mongoUrl где-нибудь выше
// настройка наших сервисов
if (process.env.VCAP_SERVICES) {
services = JSON.parse(process.env.VCAP_SERVICES);
mongoUrl = services["mongolab-n/a"][0].credentials.uri;
} else {
Глава 8. Платформа

// нам понадобится эта часть для запуска не из Cloud Foundry


mongoUrl = "mongodb://localhost/amazeriffic"
}

После того как все это заработает, можем развернуть приложение в Cloud
Foundry точно так же, как в предыдущем примере. Одно незначительное изменение
вызвано необходимостью настроить сервис MongoLab:
Create services for application?> y
1: blazemeter n/a, via blazemeter
2: cleardb n/a, via cleardb
3: cloudamqp n/a, via cloudamqp
4: elephantsql n/a, via elephantsql
5: loadimpact n/a, via loadimpact
6: mongolab n/a, via mongolab
7: newrelic n/a, via newrelic
8: rediscloud n/a, via garantiadata
9: sendgrid n/a, via sendgrid
10: treasuredata n/a, via treasuredata
11: user-provided, via
What kind?> 6
Name?> mongolab-8a0f4
1: sandbox: 0.5 GB
Which plan?> 1
Creating service mongolab-8a0f4... OK

После того как сервис Mongo создан, просто связываем его с нашим прило-
жением так же, как делали это с Redis. Как только код будет готов к связи
с удаленным сервером через URL, который предоставляется нам в переменной
окружения VCAP_SERVICES, программа должна будет работать именно так, как мы
ожи­даем.

В этой главе мы изучили, как использовать Cloud Foundry, чтобы выложить при-
ложения в Интернете. Cloud Foundry является примером платформы-сервиса
(Platform as a Service). PaaS — это компьютерная технология, которая абстрагиру-
ет настройку сервера, администрацию и хостинг, обычно с помощью программы
с интерфейсом командной строки или веб-интерфейсом.
Чтобы заставить наши приложения работать из Cloud Foundry (или любого
другого PaaS), как правило, нужно внести небольшие модификации в код, так как
его поведение различно в зависимости от того, запускается ли он локально из ра-
бочего окружения или на серверах Cloud Foundry.
Cloud Foundry представляет также внешние сервисы, такие как Redis или
MongoDB. Чтобы наши приложения работали с этими сервисами, необходимо
сначала создать сервис с помощью программы cf, а затем привязать к нему наше
приложение.
Больше теории и практики

Ø$  
$3,
В разделах с задачами в предыдущих главах мы создали простое покерное API,
которое должно идентифицировать покерные раздачи. В последней главе мы до-
бавили к нему компонент базы данных. Если у вас все получилось и приложение
работает, попробуйте модифицировать его и развернуть на Cloud Foundry.

#
В наши дни нет недостатка в облачных платформах. Одна из самых популярных
называется Heroku. Она позволяет зарегистрироваться бесплатно без использова-
ния кредитной карты. Если вы хотите добавить использование Redis или Mongo,
то карта все же понадобится, несмотря на то что у них есть варианты бесплатного
использования обоих этих сервисов.
Если вы хотите больше практики, попробуйте почитать документацию Heroku
по развертыванию с Node.js, а затем загрузить одно или несколько предложений на
этот сервис. Я бы также рекомендовал вам попробовать и другие сервисы, в том
числе Nodejitsu или Microsoft’s Windows Azure. Все они немного отличаются друг
от друга, так что сравнение их функциональности будет отличным практическим
упражнением.
Итак, если вы продвинулись по пути так далеко, то наверняка уже можете работать
и с клиентской, и с серверной стороной приложения, используя HTML, CSS
и JavaScript. А если у вас есть идея для веб-приложения, то, скорее всего, вы можете
использовать изученное и написать код, который будет делать то, что вам нужно.
Эта глава в основном о том, почему вам не надо этого делать, по крайней мере
прямо сейчас. Дэвид Парнас сказал однажды, что «один плохой программист со-
здает две новые вакансии в год». Хотя лично я полностью с этим согласен, на самом
деле я не думаю, что «плохие» программисты в самом деле плохие, просто у них
пока недостаточно опыта и практики, чтобы позаботиться о хорошей организации
своей базы кода и пригодности ее к дальнейшему развитию. Код (особенно JavaScript)
в руках неопытного программиста может очень быстро стать монолитной массой,
непригодной к работе.
Как я упоминал в предисловии, будучи новичком, вполне нормально «взламы-
вать» код, пока вы учитесь, но с опытом вы быстро поймете, что это не лучший
способ решения более сложных и серьезных инженерных проблем. Другими сло-
вами, вы еще многому должны научиться, и так будет всегда.
В то же время разработчики программного обеспечения создавали веб-приложе-
ния, основанные на базах данных, много лет, в результате чего было неизбежно по-
явление определенных шаблонов оптимизации кода. Фактически целые фреймвор-
ки для разработки, такие как Ruby on Rails, предназначены для того, чтобы заставить
разработчиков создавать приложения с использованием этих шаблонов (или, во
всяком случае, настойчиво рекомендовать им это). Поскольку наш путь по основам
подходит к концу, мы вернемся к Amazeriffic и постараемся понять, как можно сделать
имеющийся у нас сейчас код более гибким. Мы также увидим, как некоторая часть
кода может быть модифицирована, чтобы соответствовать этим шаблонам.

Начнем с клиентской части. По моему опыту, клиент — одна из частей, которую


легче всего быстро сделать непригодной к развитию. Думаю, это может быть свя-
зано с тем, что код клиентской стороны часто ассоциируется с обманчиво просты-
ми визуальными действиями, в результате чего кажется, что и реализовать его
Обобщение основных принципов действия

очень просто. Например, реализация специфического поведения, когда пользова-


тель выполняет тривиальное действие, допустим нажимает кнопку, может пока-
заться простейшей задачей кодирования. Но это не всегда так.
В нашем примере создание интерфейса с вкладками кажется довольно прямо-
линейным действием. И в данном конкретном случае это действительно так. Но мы
можем сделать его значительно более пригодным к дальнейшему развитию, слегка
переработав и следуя основным советам.

!#’
Вы всегда должны стараться как можно сильнее обобщать связанные понятия.
Я уже несколько раз упоминал в этой книге, что если какие-либо сущности в вашей
программе логически соотносятся друг с другом, лучше всего, если они будут
определенным образом связаны через реальные программные конструкции. Но
наши вкладки не соответствуют этому принципу: у них много общего, но логика
и структура разбросаны в нескольких местах. Например, мы определяем название
вкладки в HTML:
<div class="tabs">
<a href=""><span class="active">Новые</span></a>
<a href=""><span>Старые</span></a>
<a href=""><span>Теги</span></a>
<a href=""><span>Добавить</span></a>
</div>
Затем в JavaScript используем расположение вкладки в DOM, чтобы определить,
какое действие следует предпринять:
if ($element.parent().is(":nth-child(1)")) {
// генерация содержимого вкладки "Новые"
} else if ($element.parent().is(":nth-child(2)")) {
// генерация содержимого вкладки "Старые"
} else if ($element.parent().is(":nth-child(3)")) {
// генерация содержимого вкладки "Теги"
} else if ($element.parent().is(":nth-child(4)")) {
// генерация содержимого вкладки "Добавить"
}
Обратите внимание на то, что нигде в этих действиях название вкладки не ис-
пользуется. Вот пример двух программных сущностей, которые соотносятся друг
с другом, но только в голове программиста и нигде в конструкции кода. Самый
первый симптом непорядка — каждый раз, когда мы хотим добавить в пользова-
тельский интерфейс новую вкладку, нужно перерабатывать как HTML, так
и JavaScript. В главе 5 я упоминал, что из-за этого повышается вероятность воз-
никновения ошибок в нашем коде.
Как же можно это исправить? Один из способов — обобщить вкладку в виде
объекта, точно так же, как мы поступили в примере с картами в главе 5. Разница
состоит в том, что объект вкладки будет иметь строковое значение, связанное
Глава 9. Приложение

с названием вкладки, а также функциональное значение, связанное с действием,


создающим содержимое. Например, мы можем обобщить вкладку Новые в объект
такого вида:
var newestTab = {
// название новой вкладки
"name":"Новые",
// функция, создающая содержимое вкладки
"content": function () {
var $content;
// генерация и отображение содержимого вкладки Новые
$content = $("<ul>");
for (i = toDos.length-1; i >= 0; i--) {
$content.append($("<li>").text(toDos[i]));
}
return $content;
}
}

Оказывается, наличие этой структуры решает множество проблем. Наш поль-


зовательский инструмент содержит набор вкладок, и вместо того, чтобы хранить
имя вкладки и соответствующее действие в разных местах кода, мы можем создать
массив вкладочных объектов, который будет держать их вместе:
var main = function (toDoObjects) {
"use strict";
var toDos,
tabs;
toDos = toDoObjects.map(function (toDo) {
return toDo.description;
});
// создание пустого массива с вкладками
tabs = [];
// добавляем вкладку Новые
tabs.push({
"name":"Новые",
"content":function () {
// создание $content для Новые
return $content;
}
});
// добавляем вкладку Старые
tabs.push({
"name":"Старые",
"content":function () {
// создание $content для Старые
return $content;
}
});
// добавляем вкладку Теги
tabs.push({
Обобщение основных принципов действия

"name":"Теги",
"content":function () {
// создание $content для Теги
return $content;
}
});
// Создаем вкладку Добавить
tabs.push({
"name":"Добавить",
"content":function () {
// создание $content для Добавить
return $content;
}
});
};

Имея массив, где содержатся все вкладочные объекты, мы можем многократно


упростить подход к построению пользовательского интерфейса. Начнем с удаления
вкладок из HTML <div class="tabs">:
<!-- здесь определялись вкладки -->
<!-- а сейчас мы динамически создадим их с помощью JavaScript -->
</div>вообще (но оставляем элемент div):

А сейчас нужно циклически обработать массив tabs. Для каждой вкладки мы


сделаем обработчик щелчков и добавление в DOM:
tabs.forEach(function (tab) {
var $aElement = $("<a>").attr("href",""),
$spanElement = $("<span>").text(tab.name);
$aElement.append($spanElement);
$spanElement.on("click", function () {
var $content;
$(".tabs a span").removeClass("active");
$spanElement.addClass("active");
$("main .content").empty();
// здесь мы получаем содержимое из функции,
// определенной в объекте tab
$content = tab.content();
$("main .content").append($content);
return false;
});
});

Œ$-$;’#
Другая проблема приложения заключается в том, что, если кто-либо посетит его
из другого браузера и добавит какую-нибудь задачу в наш список, мы не увидим
этот новый элемент, если щелкнем на другой вкладке. Для этого нужно будет пе-
резагрузить всю страницу.
Глава 9. Приложение

Это происходит потому, что наше приложение хранит и открывает элементы


списка задач, когда страница загружается впервые, и не изменяет их до того, как
загрузка не произойдет снова. Чтобы решить эту проблему, можно сделать так,
чтобы каждая вкладка выполняла запрос AJAX при открытии. Мы можем достичь
этого, переместив действие в вызовы функции jQuery get:
tabs.push({
"name":"Новые",
"content":function () {
$.get("todos.json", function (toDoObjects) {
// создание $content для вкладки Новые
// 'return' $content больше не требуется
});
}
});

Другое решение — можно заставить программу выполнять обновления клиента в реаль-


ном времени, то есть добиться того, чтобы, когда другой пользователь обновляет стра-
ницу, на которую мы смотрим, сервер выдавал новые данные. Это несколько выходит
за рамки данной книги, но реализовать такое решение можно с использованием модуля
Node.js, который называется socket.io.

Обратите внимание на то, что таким образом меняется поведение функций, так
как теперь мы собираемся делать асинхронный запрос внутри вызова функции.
Это значит, что код теперь не будет корректно возвращать содержимое при вызове,
так как мы должны дождаться завершения вызова AJAX:
$content = tab.content();
$("main .content").append($content);

Это можно исправить несколькими способами. Самый простой — перенести


обновление DOM внутрь самой функции content:
tabs.push({
"name":"Новые",
"content":function () {
$.get("todos.json", function (toDoObjects) {
// создание содержимого вкладки Новые $content
// обновление DOM здесь
$("main .content").append($content);
});
}
});

Но это решение мне не нравится по двум причинам. Первая из них скорее эсте-
тическая: функция content должна создавать и возвращать содержимое вкладки,
а не влиять на DOM. Иначе мы должны переименовать ее в getContentAndUpdateThe
DOM.
Обобщение основных принципов действия

Другая причина немного важнее: если в итоге мы хотим добиться чего-то боль-
шего, а не просто обновления DOM, нужно по такому же принципу изменить
каждую функцию content для каждой вкладки.
Этих двух зайцев можно убить одним выстрелом: реализацией протяженного
подхода, который мы использовали ранее для асинхронных операций. Пусть вы-
зывающая функция включает в себя и обратный вызов, а обращаться к ней мы
будем внутри функции content:
// создаем функцию content
// так, что она принимает обратный вызов
tabs.push({
"name":"Новые",
"content":function (callback) {
$.get("todos.json", function (toDoObjects) {
// создаем $content для Новые
// создаем обратный вызов с $content
callback($content);
});
}
});
// ...
// а сейчас отправляем обратный вызов внутри вызывающей функции
tab.content(function ($content) {
$("main .content").append($content);
});

Это, наверное, самый известный способ решения, который вы можете найти в со-
обществе Node.js, но и другие тоже набирают популярность, например Promises
(Потенциалы) и Reactive JavaScript (Реактивный JavaScript). Если асинхронные
операции становятся сложнее и вы обнаруживаете себя в омуте обратного вызова1
(как часто называют эту ситуацию), то лучше всего хорошенько изучить эти вари-
анты решения.

#
Сейчас, когда вкладки создаются с помощью AJAX, мы можем избавиться от бол-
тающихся повсюду костылей совместимости. Раньше серверу приходилось возвра-
щать список задач ToDo целиком, так как именно этого ожидал клиент, независи-
мо от времени добавления. Сейчас вместо этого мы будем переключаться на
вкладку Новые при добавлении какого-либо объекта ToDo.
Кнопка для добавления кода в настоящее время выглядит вот так:
$button.on("click", function () {
var description = $input.val(),
tags = $tagInput.val().split(","),

1
В оригинале callback hell, то есть буквально «ад обратного вызова». Но омут как нечто
крутящееся для данной ситуации подходит больше, а черти есть и тут, и там. — При­
меч. пер.