Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
Пьюривал
Основы разработки веб-приложений
Серия «Бестселлеры 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
º Платформа........................................................................................ 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
Оглавление
º Интерактивность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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
Оглавление
º Сервер. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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
Оглавление
º Платформа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Cloud Foundry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Регистрация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Подготовка приложений к развертыванию в Сети . . . . . . . . . . . . . . . . . . . . . 235
Развертывание приложения. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .236
Получение информации о приложениях. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Обновление приложения. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Удаление приложений из Cloud Foundry. . . . . . . . . . . . . . . . . . . . . . . 240
Оглавление
º Приложение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .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 вы найдете подробную информацию
о наших книгах.
В начале 2008 года, через шесть лет после окончания школы и работы учителем на
полставки, мне очень хотелось стать преподавателем компьютерных дисциплин на
полный день. Очень быстро выяснилось, что место преподавателя найти нелегко,
а получение хорошей работы зависит от удачи в большей степени, чем от чего-либо
еще. Ну что ж, я поступил так, как поступает любой уважающий себя академик,
столкнувшись с удручающим положением на академическом рынке труда, а имен-
но: решил повысить свою конкурентоспособность с помощью изучения разработки
веб-приложений.
Это, конечно, звучит странно. Кроме всего прочего, к тому моменту я уже около
девяти лет изучал компьютерные дисциплины и, более того, свыше шести лет учил
студентов разрабатывать программное обеспечение (ПО). Разве я не должен был
хорошо знать, как создавать веб-приложения? Похоже, что нет, так как существу-
ет определенный разрыв между практической ежедневной работой по разработке
ПО и программированием как учебной дисциплиной, изучаемой в колледжах и уни-
верситетах. Фактически мои знания по веб-разработке были ограничены HTML
и в некоторой степени CSS, который я в то время изучал самостоятельно.
Предисловие
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, однако в некоторых
случаях пришлось отклониться. Например, для текстовых строк я предпочитаю
двойные, а не одинарные кавычки в первую очередь потому, что предполагаю
Условные обозначения
ß#’
В книге применяются следующие условные обозначения.
Курсивный шрифт
Им обозначаются новые термины и понятия.
Шрифт для названий
Используется для обозначения 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
Выражения признательности
Œ#’$
Огромное спасибо ребятам из департамента информатики в Университете Се-
верной Каролины в Эшвилле за возможность провести этот курс дважды. И, ко-
нечно, спасибо студентам за терпение, поскольку материал постепенно эволюци-
онировал.
Спасибо редактору Мег Бланшетт за огромные усилия по удержанию меня
в нужном русле и, конечно, за бесконечное терпение из-за нарушения сроков. Я буду
скучать по нашей еженедельной электронной переписке!
Спасибо Саймону Сент-Лорену за множество советов в самом начале работы
и подсказки о том, что может подойти издательству O’Reilly.
Сильван Кавано и Марк Филипс внимательно прочитали каждую главу и вы-
сказывали множество очень полезных замечаний на протяжении всей работы.
Эмили Уотсон прочитала первые четыре главы и внесла очень много дельных
предложений. Майк Уилсон прочел последние четыре главы и дал бесценные тех-
нические советы. Я бесконечно благодарен всем вам и надеюсь когда-нибудь от-
платить вам тем же.
Боб Бенитс, Уилл Бласко, Дэвид Браун, Ребекка Дэвид, Андреа Фей, Эрик Хоуи,
Брюс Хауман, Джон Максвелл, Сюзан Рейсер, Бен Роузен и Вэл Скарлата прочи-
тали множество версий материала и дали много полезных рекомендаций. Я искрен-
не благодарен за потраченные вами время и усилия. Вы молодцы!
Несмотря на множество опытных рецензентов и друзей, которые просматрива-
ли материал, практически невозможно написать подобную книгу без каких-то
технических ошибок, опечаток и неверных решений. Я принимаю на себя полную
ответственность за все это.
ł
œ##
Инструмент, с которым вы будете сталкиваться чаще всего, — текстовый редактор.
Об этом необходимом элементе технологии порой незаслуженно забывают, но на
самом деле это самый важный инструмент в вашем рабочем чемоданчике, посколь-
ку это программа, с помощью которой вы взаимодействуете со своим кодом. По
скольку код формирует конкретные элементы конструкции приложения, очень
важно, чтобы было как можно проще его создавать и редактировать. К тому же вы,
как правило, будете редактировать несколько файлов одновременно, так что очень
важно, чтобы текстовый редактор предоставлял возможность быстро и просто
перемещаться по файловой системе.
В прошлом вы, вероятно, тратили много времени на написание документов
и редактирование текста в программах наподобие Microsoft Word или Google
Docs. Это не лучшие примеры редакторов, о которых мы говорим. Эти редакторы
концентрируются скорее на возможностях форматирования текста, чем на мак-
симальной простоте его редактирования. Редактор, который будем использовать
Текстовые редакторы
ß6XEOLPH7H[W
Sublime Text (или для краткости Sublime) — популярный многофункциональный
текстовый редактор, идеально подходящий для веб-разработки. Кроме этого, у него
есть преимущество кросс-платформенности, то есть он работает одинаково эффек-
тивно с Windows, Mac OS и Linux. Это платная программа, но вы можете скачать
бесплатную версию и использовать ее сколько захотите. Если этот редактор вам
понравится и вы будете пользоваться им постоянно, рекомендую оплатить лицен-
зию.
Чтобы установить Sublime, зайдите на http://www.sublimetext.com и щелкните на
ссылке Download вверху страницы. Там вы найдете программы установки для всех
основных платформ. Хотя (на момент написания книги) Sublime Text 3 находится
на бета-тестировании, я рекомендую попробовать именно его. Я использовал его
для всех примеров и скриншотов из этой книги.
#6XEOLPH7H[W
После установки и запуска Sublime Text вы увидите экран примерно такого вида,
как показано на рис. 1.3.
Текстовые редакторы
Вероятно, в первую очередь вы захотите создать новый файл. Это можно сделать
с помощью меню FileNew. Вы можете также нажать 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
$
Представьте, что вы написали с помощью текстового редактора большой фрагмент
художественной повести. Вы периодически сохраняете работу, чтобы случайно не
Контроль версий
ß*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. Рабочий процесс
## 81,;
Для Git есть графические интерфейсы, но гораздо более эффективно научиться
работать с ним с помощью командной строки. Перед тем как вы начнете учиться
Контроль версий
º’"
Очень важно помнить, что, работая в командной строке, вы всегда выполняете
команды из какой-либо папки. Первый вопрос, который вы должны задать себе,
попав в интерфейс командной строки: «В какой папке я нахожусь?» Получить
ответ можно двумя способами. Первый — использовать команду pwd, что означает
print working directory («вывести рабочую директорию» (то есть папку)). Вывод
будет выглядеть примерно так:
hostname $ pwd
/Users/semmy
hostname $ ls
Projects
ø
И наконец, вам может понадобиться создать новую папку для хранения своих
проектов, которые вы будете создавать с помощью этой книги. Для этого исполь-
зуйте команду 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
## ##
Веб-разработка (и программирование в целом) — очень абстрактная форма твор-
чества. В целом это означает, что для эффективного и рационального труда вы
должны развивать абстрактное мышление. Во многом эта способность требуется
для быстрого создания ментальных моделей новых идей и структур. Одна из луч-
ших ментальных моделей, которая применяется во множестве разных ситуаций, —
древовидная диаграмма.
Древовидная диаграмма — это простой способ визуализации любого типа
иерархической структуры. Поскольку файловая система в UNIX является иерар-
хической структурой, будет естественно начать упражнения в ментальной визуа-
лизации с нее. Например, представьте себе, что папка Home (Домашняя) содержит
три другие папки: Documents (Мои документы), Pictures (Мои рисунки) и Music (Моя
музыка). Внутри папки Pictures находятся пять картинок. Внутри каталога Documents
содержится другой каталог — Projects (Проекты) (рис. 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. Рабочий процесс
ø!*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
Нам не придется ничего делать в этой папке, так что сейчас мы можем смело ее
проигнорировать. Но со скрытыми файлами еще встретимся, так что постарайтесь
запомнить полезный флаг –a для команды ls.
!
Откроем Sublime Text (если он все еще открыт со времени чтения предыдущего
раздела, закройте его и откройте снова). Затем откройте папку, версии в которой
мы будем контролировать. Чтобы сделать это, просто выбираем папку в диалоговом
окне Open программы Sublime вместо конкретного файла. Когда вы открываете
1
Системное сообщение: «Создано пустое хранилище Git в /Users/semmy/Projects/Chap-
ter1/.git». — Примеч. пер.
Контроль версий
Чтобы создать новый файл в папке Chapter1, щелкните правой кнопкой (или
выполните Command-щелчок в Mac OS) на названии Chapter1 в файловой навига-
ционной панели, а затем выберите New File из контекстного меню. Как и ранее,
будет создан новый файл, но, когда вы сохраните его, по умолчанию будет исполь-
зована папка Chapter1. Назовем файл index.html.
После того как файл получил имя, сделайте на нем двойной щелчок и добавьте
строку Hello, World! в верхнюю часть файла (рис. 1.11).
Здесь довольно много информации! Больше всего нас интересует блок, поме-
ченный Untracked files (Неотслеживаемые файлы). Здесь перечислены файлы,
которые находятся в рабочей папке, но пока не включены в контроль версий.
Мы видим, что файл index.html здесь и он может быть зафиксирован (committed)
в хранилище Git.
ı ##
Мы заинтересованы в отслеживании изменений в файле index.html. Чтобы иметь
возможность делать это, последуем инструкциям, которые выдал Git, и добавим
файл в хранилище, используя команду git add:
hostname $ git add index.html
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
Легко забыть включить флаг –m и сообщение для коммита в процессе заливки измене-
ний в систему контроля версий. Если это произойдет, вы, скорее всего, обнаружите себя
внутри текстового редактора Vim (который обычно по умолчанию является системным
редактором). Если это так, вы можете покинуть его нажатием двоеточия (:), а затем
ввести q! и нажать Enter, чтобы выйти.
1
Системное сообщение: «От бранч-мастера. Начальный коммит. Изменения для коммита:
(используйте "git rm --cached <file>..." для отката). Новый файл: index.html». — Примеч. пер.
2
Системное сообщение: «[master (root-commit) 147deb5] Начальный коммит. 1 файл
изменен, 1 добавлен (+). Создан модуль 100644 index.html». — Примеч. пер.
3
Системное сообщение: «От бранч-мастера: нет изменений для коммита (рабочая
директория актуальна)». — Примеч. пер.
Глава 1. Рабочий процесс
$&!
Мы сделали целых два коммита проекта и можем откатить систему к их состояни-
ям в любое время. В разделе «Больше теории и практики» далее я привожу ссылку,
которая покажет вам, как откатить предыдущий коммит обратно и начать кодиро-
вание из этой точки. А сейчас изучим еще одну очень полезную команду. Мы можем
посмотреть историю коммитов, используя 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 добавление (+)». —
Примеч. пер.
Контроль версий
ø#
На всякий случай, чтобы избежать неясностей, я хотел бы подробно остановиться
на разнице между сохранением файла в текстовом редакторе и фиксированием
изменений в системе контроля версий. Когда вы сохраняете файл, по сути, вы пе-
реписываете его на диск компьютера. Это значит, что вы больше не получите до-
ступа к старой версии файла, если только редактор не предоставляет возможности
построения истории ревизий.
Фиксирование же в хранилище Git позволяет вам отслеживать все изменения,
которые вы сделали с последнего раза, когда добавляли новую версию файла. Это
значит, что вы всегда можете вернуться к предыдущей версии, если обнаружили,
что сделали трудноисправимую ошибку в текущей версии файла.
В данный момент у вас, вероятно, сложилось впечатление, что Git сохраняет код
как линейную последовательность коммитов. Сейчас это действительно так: мы
изучили ту часть Git, которая позволяет создать такое хранилище, где один коммит
следует строго за другим коммитом. Мы можем назвать первый коммит родитель-
ским, а второй — дочерним. Хранилище Git с четырьмя коммитами показано на
рис. 1.12.
Следует отметить, однако, что коммит, по сути, — это серия инструкций по
обновлению вашего проекта до следующей версии. Другими словами, Git на самом
деле вовсе не сохраняет каждый раз содержимое каталога полностью, как если бы
вы скопировали одну папку на компьютере в другую. Вместо этого он просто фик-
сирует, что именно должно быть изменено: например, коммит может хранить ин-
формацию вроде «добавь строку с текстом Goodbye, World!» вместо сохранения
всего файла. Так что лучше представлять себе хранилище Git как серию инструк-
ций. Вот почему мы пишем сообщения для коммитов в форме императива — вы
можете представлять себе коммит как последовательность указаний для перевода
проекта из одного состояния в другое.
Почему все это так важно? Дело в том, что хранилище Git может иметь гораздо
более сложную структуру. Коммит может иметь более одного «ребенка» и факти-
чески более одного «родителя». На рис. 1.13 показан пример более сложного
хранилища Git, где иллюстрируются оба этих случая.
Глава 1. Рабочий процесс
Хотя мы еще не изучили команд Git для создания структуры наподобие этой,
вам придется поближе познакомиться с ними, если вы захотите продолжить путе-
шествие по миру веб-разработки. Я уверен, вы сделали вывод о необходимости
составления визуальной схемы хранилища Git, чтобы не запутаться при возраста-
нии сложности.
Ø#
Последний инструмент, с которым мы будем регулярно сталкиваться, — браузер.
Прежде чем изучать создание веб-приложений, которые будут запускаться в браузере,
Подведем итоги
Ø$
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. Рабочий процесс
ß$ *LW
Git — невероятно мощный инструмент. Мы только слегка коснулись его возмож-
ностей. К счастью, Скотт Чейкон написал Pro Git (Apress, 2009) — прекрасную
книгу, где подробно рассматривается множество аспектов Git. В первых двух гла-
вах описывается несколько функций, которые помогут вам более эффективно
продвигаться в изучении материала этой книги, включая откат к версиям, предва-
рительно зафиксированным в хранилище.
В третьей главе книги Чейкона детально описана концепция бранчей. Бранчи
несколько выходят за рамки моей книги, но я упоминал о них ранее. Рекомендую
как следует изучить эту тему, так как возможность быстро и просто разветвить
хранилище — одна из лучших функций Git.
*LW+XE
GitHub — это онлайн-сервис, в котором находится ваше хранилище Git. Если вы
храните код открыто (open source), оно бесплатно. Если же хотите создать защи-
щенное хранилище Git, самый дешевый тарифный план — $7 в месяц. Я рекомен-
дую начать с бесплатного плана и исследовать хранилище Git на GitHub.
Страница помощи GitHub поможет вам настроить аккаунт GitHub и связать
его с вашим хранилищем Git. Там также находятся тонны полезной информации
и о Git, и о GitHub. Пользуйтесь ею, чтобы начать действовать.
ø
+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.
Очень полезно постараться выучить клавиатурные сокращения, так как это поможет
работать быстрее и рациональнее. Для открытия файла в Chrome вам нужна команда
Command+O, если вы работаете в Mac. В Windows эта же команда — Ctrl+O.
œS!D#
А сейчас внесем небольшие изменения, добавив абзац текста Lorem ipsum — это
простой текстовый наполнитель, который мы можем заменить нужным нам
Привет, HTML!
1
В Интернете много сервисов для генераторов текста lorem ipsum, в том числе на русском
языке. Используя кириллицу, при сохранении файла выставляйте кодировку UTF-8,
иначе символы могут отображаться некорректно. — Примеч. пер.
Глава 2. Структура
#
Мы познакомились с некоторыми примерами основных тегов и комментариев, что
же еще мы можем включить в разметку?
Сначала можем расширить тег <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>
<!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!
несколько раз. Для начала добавьте несколько абзацев текста 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 для выявления проблем
$+70/
’#’’
Как я уже сказал в предыдущем разделе, текстовое содержание HTML-документа
обычно заключено в пару тегов. Открывающий тег выглядит как <html>, а закрыва-
ющий — как </html>. Точное имя тега означает тип элемента DOM, который он
собой представляет.
Если ваш документ становится слишком длинным, могут возникнуть некото-
рые проблемы. Например, представьте следующий HTML-документ, являющий-
ся определенным обобщением предыдущего примера, с несколькими новыми
тегами:
<!doctype html>
<html>
<head>
Рис. 2.5. Дерево, представляющее схему DOM
Глава 2. Структура
Использование валидации HTML для выявления проблем
Вы, наверное, заметили вкладку Validate by Direct Input. Щелкнув на ней, мы мо-
жем вырезать и вставить наш HTML-код в появившееся текстовое поле. После
вставки кода нужно нажать большую кнопку Check.
Начнем с примера с Lorem ipsum, приведенного ранее. Если он не содержит
никаких ошибок, мы увидим что-то похожее на рис. 2.7.
Рис. 2.7. Валидатор W3C HTML после проверки примера с Lorem ipsum
Использование валидации HTML для выявления проблем
Валидируя HTML, вы, скорее всего, получите три предупреждения, даже если ошибок
нет. Первое из них сообщает, что валидатор использует правила для проверки HTML5.
Несмотря на то что стандарт HTML5 относительно стабилен, в нем все же могут быть
изменения, о чем и уведомляет это сообщение.
Другие два относятся к кодировке символов и в данный момент могут быть проигнори-
рованы. Если вам интересно, одно из предупреждений содержит ссылку на обучающий
материал по кодировке символов, который научит вас изменять кодировку HTML-доку-
мента.
$PD]HULI¿F
В оставшейся части главы мы с вами создадим HTML для учебного веб-приложе-
ния. Таким образом мы убьем сразу двух зайцев: опробуем на настоящем проекте
рабочий процесс, изученный в предыдущей главе, а также рассмотрим еще несколь-
ко важных тегов HTML и отображаемых ими элементов.
#
Учебное приложение называется Amazeriffic, от слов amazing — «изумительный»
и terrific — «невероятный». Это название, конечно, немного глуповато, но не хуже
названия любой другой компании, размещающейся в наши дни в Кремниевой до-
лине. Предназначение продукта Amazeriffic состоит в отслеживании и категоризации
набора задач (по сути, это список необходимых дел). Далее в этой книге мы и в самом
деле поработаем над реализацией проекта, похожего на этот, но пока не освоимся
достаточно с HTML, сконцентрируемся на главной странице продукта. Страница,
которую мы создадим, будет похожа на рис. 2.10.
Помните, что HTML задает структуру документа. Это значит, что, даже если
мы видим здесь множество стилистических элементов (разные шрифты, цвета
и даже верстка элементов), по большей части их следует игнорировать, так как
к HTML они не имеют никакого отношения. Пока мы с вами концентрируемся
исключительно на структуре документа.
Amazeriffic
Рис. 2.10. Страница Amazeriffic, которую мы создадим в течение этой и последующих глав
Œ’#!$&
#
После идентификации всех структурных элементов нужно продумать, как они
будут сочетаться друг с другом. Чтобы сделать это, построим древовидную диа-
грамму для структуры, которая определит содержимое разных элементов. На рис. 2.12
показано древовидное представление возможной структуры.
Глава 2. Структура
ł’#
Теперь, когда у нас есть древовидное представление страницы (как в голове, так
и на бумаге), очень легко написать код HTML, если мы знаем теги, необходимые
для всех элементов на странице. Поскольку вы еще не видели некоторые теги,
которые здесь упомянуты, я буду объяснять их назначение по ходу дела.
Прежде чем что-либо делать, создадим каталог для хранения проекта. Если вы
следовали инструкциям, приведенным в главе 1, у вас уже есть папка Projects в до-
машней папке (если вы в Mac OS или Linux) либо в каталоге Documents (если ра-
ботаете в Windows). Перейдем в эту папку из командной строки Tеrminal application
в Mac OS или из Git Bash в Windows. Используем команду cd:
hostname $ cd Projects
hostname $ ls
Amazeriffic
hostname $ cd Amazeriffic
Таким образом, сейчас мы находимся в новой папке проекта (в чем можем убе-
диться с помощью команды pwd). Следующая очень важная задача — поместить эту
папку в систему контроля версий. Создадим проект Git с помощью команды git
init:
hostname $ git init
Initialized empty Git repository in /Users/semmy/Projects/Amazeriffic/.git/
Сохраните файл и еще раз откройте страницу в браузере. Сделав это, увидите
нечто похожее на рис. 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
#
# 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. Структура
Ссылки в элементе 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. Он
содержит текстовое описание изображения. Это сделано для обеспечения доступ-
ности нашей страницы для слабовидящих пользователей, которые часто применя-
ют программы для чтения содержимого экрана при пользовании Интернетом.
На этом этапе неплохо будет прогнать код через валидатор и убедиться, что вы
ничего случайно не пропустили. Если все хорошо, сделайте еще один коммит в наше
хранилище 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>
Больше теории и практики
Ø$
В коде Amazeriffic я оставил несколько ошибок. Рекомендую вам исправить их
и заставить код работать настолько хорошо, насколько вы сможете. Помните: если
Глава 2. Структура
##
Нарисуйте древовидную диаграмму для следующего 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. Я рекомендую углубленно изучить документацию
и расширенные возможности.
ø$
&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>
ı#
Файл CSS — это коллекция наборов правил, а набор правил — это просто ряд ука-
заний относительно стилевого оформления, которые применяются к какому-либо
виду элементов в DOM (как вы помните, это иерархическая система объектов
в HTML-документе). Набор правил состоит из селектора — это может быть, на-
пример, название тега, открывающей фигурной скобки, списка правил и закрыва-
ющей фигурной скобки. Каждое правило состоит из определенного свойства,
после которого следуют двоеточие, значение этого свойства (или список значений,
разделенных пробелами), после чего ставится точка с запятой, например:
body {
width: 800px;
background: lightcyan;
color: #ff0000;
}
входят три правила: первое определяет значение свойства 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; /* Устанавливаем красный цвет для элементов*/
}
##’
В большинстве своем элементы HTML отображаются двумя способами. Пер-
вый — внутристрочный, который применяется, например, к элементам span. Это
значит, кроме всего прочего, что содержимое тега будет появляться на той же стро-
ке, что и окружающие его элементы:
Глава 3. Стиль
<div>
Этот абзац и это <span>слово</span> отображаются внутри строки. Эта
<a href="http://www.example.com">ссылка</a> тоже отображается внутри строки.
</div>
Если мы добавим этот элемент в тег body в нашем index.html, то страница будет
отображаться, как показано на рис. 3.3.
Рис. 3.4. Пример блочного элемента, находящегося внутри другого блочного элемента
Привет, CSS!
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.
ø#
Самый важный аспект в CSS — эффективное использование селекторов. Мы уже
видели базовый тип селекторов, в качестве которых используется название тега —
для применения стиля выбираются все теги с таким именем. Например, рассмотрим
следующий HTML:
Селекторы
<body>
<h1>Привет!</h1>
<p>Это абзац.</p>
<p>Это второй абзац.</p>
</body>
Первый набор правил оформляет элемент h1, а второй — оба элемента p. Во мно-
гих случаях нам будет требоваться именно это. Однако в других мы можем захотеть
применить к первому и второму абзацам разные стили.
#
В главе 2 мы уже видели, что можно добавить атрибут class к тегам <div>, чтобы
отличать один от другого. На самом деле мы можем добавить класс к любому эле-
менту DOM. Например, можем переписать предыдущий HTML следующим обра-
зом:
<body>
<h1>Привет!</h1>
<p class="first">Это абзац.</p>
<p class="second">Это второй абзац.</p>
</body>
типа элементов (как обычно и происходит), то мы можем опустить имя тега и прос-
то использовать класс:
.first {
color: red;
margin: 10px;
padding: 20px;
}
Элементу а может быть присвоен стиль точно так же, как любому другому эле-
менту DOM, — с помощью CSS. Например, мы можем создать файл CSS, который
изменяет цвета всех ссылок:
a {
color: cornflowerblue;
}
В этом коде 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>
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;
}
&!’
Мы познакомились с элементами, влияющими на базовый стиль элементов 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;
Селекторы
background: gray;
}
nav {
/* Уберите пометку комментария со следующей строки, если хотите иметь черную гра-
ницу */
/* border: 3px solid black; */
width: 200px;
float: right;
}
p {
margin: 0; /* Ноль от границы по умолчанию */
}
Результат будет похож на рис. 3.8.
Так произошло потому, что сумма пикселов в элементе 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>
выбирает все элементы DOM. Он также устанавливает для фона элемента разные
оттенки серого цвета (рис. 3.11):
* {
margin: 0;
padding: 0;
}
nav {
float: left;
background:darkgray;
}
main {
background:lightgray;
}
footer {
background:gray;
}
Рис. 3.11. Обратите внимание: подвал находится под секцией main, вместо того чтобы
располагаться снизу всего содержимого страницы
ł
В прошлом работа с нестандартными шрифтами на сайтах была очень проблема-
тичной, потому что вы были ограничены теми из них, которые имелись на компью-
терах ваших пользователей. Веб-шрифты значительно упростили эту задачу, так
как вы можете запрашивать их прямо из Интернета, когда в них возникает необхо-
димость. В 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>
Работа со шрифтами
HTML допускает применение как одинарных ('), так и двойных (") кавычек для ограни-
чения строк и значений. В примерах в этой книге я старался придерживаться использо-
вания двойных кавычек, но в скопированном и вставленном коде Google видим одинар-
ные. Поскольку они эквивалентны друг другу, можете исправить их на двойные или
оставить одинарными, значения это не имеет.
1
На рисунке показаны шрифты, выбранные с применением фильтра Script:Cyrillic
(находится на панели слева, значение по умолчанию — Latin). Большинство кириллических
шрифтов, разумеется, поддерживают латинский алфавит, но, к сожалению, не наоборот. —
Примеч. пер.
Глава 3. Стиль
ß
С одним спорным инструментом, который называется сбросом 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>
<head>
<title>Пример 8 — Использование сброса CSS</title>
<link href="reset.css" rel="stylesheet" type="text/css">
</head>
Сейчас, перезагрузив страницу, мы увидим нечто похожее на рис. 3.16.
Рис. 3.16. Простая страница, где с помощью сброса удалены все стили по умолчанию
$&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.
CSS Lint дает несколько больше предупреждений, чем инструмент для HTML,
изученный нами в главе 2, но здесь есть опции, позволяющие настроить уровень
предупреждений. Вы можете увидеть эти опции, щелкнув на стрелке вниз справа
на большой кнопке LINT! на главной странице. Опции разбиты на категории соглас-
но причинам предупреждений. Например, опция Disallow universal selector (Запрещать
универсальные селекторы) находится в категории Performance (Производитель-
ность). Это значит, что если вы готовы допустить чуть более медленную загрузку
вашей страницы ради какой-то особенной функциональности CSS, то отключаете
эту опцию и предупреждаете Lint об этом.
Обычно я оставляю все опции в CSS Lint выбранными, но вы можете настроить
и свои варианты. Я бы рекомендовал не выключать ни одну из них, а, получив
предупреждение, уделить немного времени поиску в Google, чтобы разобраться
Взаимодействие и решение проблем с Chrome Developer Tools
Œ
&KURPH’HYHORSHU
7RROV
В абсолютно любом программном обеспечении очень часто наблюдается ситуация,
когда все идет совсем не так, как надо. При использовании CSS это чаще всего
означает, что страница выглядит в браузере совершенно иначе, чем вы ожидали,
а в чем проблема — непонятно. К счастью, браузер Chrome включает в себя отлич-
ный набор инструментов, которые помогут решить проблемы как с CSS, так
и с HTML.
Начнем с открытия файла margin_border_padding.html в Chrome. Затем откройте
меню View, выберите подменю Developer и щелкните на опции Developer Tools1. В ре-
зультате внизу страницы откроются Инструменты разработчика Chrome. Если вклад-
ка Elements вверху панели неактивна, щелкните на ней — таким образом вы увиди-
те нечто похожее на рис. 3.18.
1
На сегодняшний момент: кнопка панели Chrome в правом верхнем углуTools (Инст
рументы)Developer Tools (Инструменты разработчика) или сочетание клавиш
Ctrl+Shift+I. — Примеч. пер.
Глава 3. Стиль
Рис. 3.19. Главный элемент div выделен, и вы можете видеть соответствующий ему набор правил
каждого из правил. Вы можете снять их, отменив таким образом какое-либо пра-
вило. Если возникли какие-то проблемы с вашим 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!
1
Сообщения о коммитах сверху вниз: «Добавлены содержание и структура подвала»,
«Добавлено содержание секции main», «Добавлено содержание секции header», «Добавлен
скелет структуры Amazeriffic», «Начальный коммит». — Примеч. пер.
Глава 3. Стиль
ø
Исследуем стиль для Amazeriffic. Как вы можете видеть, содержимое страницы
разделено на две колонки. Первая занимает около двух третей области содержания,
а вторая — только одну треть. Затем обратите внимание на то, что содержание
разбито по вертикали примерно по три строки. Это определяет стилистическую
сетку для содержания, которая является одним из базовых подходов при разработ-
ке компоновки страницы.
Еще один аспект, который нелегко продемонстрировать в книге, — это дизайн
фиксированной ширины. Это значит, что при изменении ширины окна браузера
содержимое страницы остается выровненным по центру и сохраняет горизонталь-
ный размер. На рис. 3.21 и 3.22 вы можете видеть Amazeriffiic в окнах браузера
разного размера.
Это значит, что его дизайн не является адаптивным — так называется современный
подход к верстке CSS, обеспечивающий перестроение макета в зависимости от шири-
ны окна браузера. Это важно при веб-разработке, так как многие люди будут просмат-
ривать ваш сайт на мобильном телефоне или планшете. Мы не будем заниматься этим
сейчас, но вы можете испробовать несколько инструментов, которые упрощают раз-
работку адаптивного дизайна, выполнив упражнения в конце этой главы.
Наша цель — создать фиксированный дизайн в две колонки с помощью CSS.
Для начала нужно, чтобы строки (в данном случае это элементы header, main и footer)
имели фиксированную ширину — значение свойства width. Первое, что необходи-
мо сделать, — создать наборы правил для этих элементов. Добавим в файл style.css
набор правил, который выглядит следующим образом:
header, main, footer {
min-width: 855px;
}
Стилизуем Amazeriffic!
Сейчас мы видим, что вся страница стала желтого цвета, так что нужно изме-
нить фоновый цвет элементов header и main на белый. Чтобы сделать это, мы
должны добавить два новых набора правил для элементов header и main в допол-
нение к тому, который содержит свойство min-width. Разделим их, так как далее
придется добавить отдельные стилистические правила в каждый набор, как мы
увидим позднее:
header {
background: white;
}
main {
background: white;
}
На момент написания книги тег <main> может отображаться в браузере Safari не совсем
корректно. Если вы столкнулись с проблемами при использовании этого браузера, по
пробуйте добавить набор правил для элемента main, включающий правило display:
block;.
А сейчас, когда у нас уже заданы какие-то базовые стили, было бы неплохо до-
бавить эти изменения в хранилище Git. Начните с проверки статуса. Единственный
поменявшийся файл — style.css. Добавьте этот файл для фиксации с помощью
команды add, а затем отправьте коммит с поясняющим сообщением.
Стилизуем Amazeriffic!
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!
Если страница выглядит так же, как на рисунке, и вы выявили все возможные
проблемы, пропустив 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.
Ø$
В 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 адаптивным с помощью одного из них.
Помните об этих фреймворках как о стартовых точках для будущих проектов.
$
-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!
Файл app.js наиболее интересен для нас. Его содержание показано в следующем
фрагменте кода. Прежде чем мы поговорим о нем, наберите этот код в точности как
в книге, а затем откройте файл index.html в браузере.
var main = function () {
"use strict";
window.alert("hello, world!");
};
$(document).ready(main);
ø
Посмотрим, к какому пользовательскому интерфейсу нам нужно стремиться. Он
показан на рис. 4.2.
Первое интерактивное приложение
ø$
Оформление будет совсем несложным. Полезно будет использовать сброс 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);
%’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);
Но даже если мы используем в 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());
Вы, вероятно, не подозреваете, что в нашем коде уже есть баги, но, как ни странно,
это именно так! Полезно будет уделить некоторое время продумыванию ожидаемого
поведения нашего приложения и проверке того, соответствует ли ему действитель-
ное положение вещей.
Если вы все еще его не нашли, вот подсказка: когда мы щелкаем на кнопке до-
бавления при отсутствии текста в поле ввода, программа 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.
’
Еще одна серьезная проблема пользовательского взаимодействия — то, что поле
ввода не очищается после того, как пользователь щелкнет на кнопке. Если он хочет
Первое интерактивное приложение
(QWHU
Еще одна вещь, которой обязательно будут ожидать пользователи, — отправка
комментария при нажатии клавиши Enter на клавиатуре. Это привычное положение
вещей, например, при работе с мессенджерами.
Как мы можем этого добиться? Нужно добавить дополнительный обработчик
событий, который улавливает нажатие клавиши непосредственно в элементе вво-
да. Мы можем добавить его сразу после обработчика нажатия:
$(".comment-input input").on("keypress", function (event) {
console.log("hello, world!");
});
Обратите внимание на то, что в слове 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("");
}
});
’!’
Сейчас мой (и ваш тоже) файл 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. Интерактивность
!’M4XHU\
Каким насыщенным был этот пример, но, надеюсь, вы все-таки с ним справились!
С его помощью мы немного познакомились с множеством возможностей jQuery.
А сейчас сделаем шаг назад и поговорим о том, с чем мы сейчас работали. Вообще
jQuery предоставляет нам три основные возможности:
упрощенный и четко организованный способ манипулирования элементами DOM;
способ последовательной обработки событий DOM;
упрощенный подход к AJAX.
Изучим две первые из них в этой главе, а третью — в следующей. Нужно отме-
тить также, что в jQuery есть огромное количество сторонних плагинов, с помощью
которых можно быстро и просто улучшить любой сайт.
ø
Перед тем как приступить, уделим немного времени обсуждению рабочего процес-
са. В целом файлы клиентской стороны приложения будут делиться на HTML, CSS
Общие сведения о jQuery
ø#
Как мы уже убедились в предыдущем примере, селекторы в jQuery очень похожи
на селекторы CSS. Фактически можно использовать любой селектор CSS в качестве
селектора для jQuery. Например, вот эти селекторы jQuery будут действовать точ-
но так, как мы ожидаем:
$("*"); // выбираются все элементы в документе
$("h1"); // выбираются все элементы h1
Глава 4. Интерактивность
ß%’20
После того как мы с помощью селекторов успешно добавили элементы в DOM,
можно начать управление ими. В jQuery это очень просто.
%’20
Для начала очень полезно запомнить древовидную ментальную модель DOM.
Например, рассмотрим следующий фрагмент HTML:
<body>
<h1>Это пример!h1>
<main>
</main>
<footer>
</footer>
</body>
Обратите внимание на то, что элементы main и footer пусты. Мы можем нари-
совать для такого примера диаграмму, похожую на показанную на рис. 4.3.
Рис. 4.3. Древовидная диаграмма элементов DOM перед переходом к управлению jQuery
Общие сведения о jQuery
После того как мы создали элемент, можем добавлять туда разные объекты,
включая другие HTML-элементы. Например, можем добавить текст в элемент
абзаца вот так:
$newParagraphElement.text("Это абзац");
Рис. 4.5. DOM после того, как элемент p был добавлен в подвал
Рис. 4.6. DOM, содержащая поддерево с элементом ul, не связанное с остальной частью
Рис. 4.7. DOM, куда входит связанное поддерево, представляющее собой маркированный список
А что, если нужно набрать все элементы массива? Один из способов сделать
это — использовать свойства массива 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 для выявления возможных проблем
$-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. Интерактивность
Но в этом коде есть еще одна скрытая проблема. К счастью для нас, существует
аналог CSS Lint для JavaScript, называемый вполне логично JSLint. Домашняя
страница JSLint показана на рис. 4.9.
1
Автор использует жаргонные слова truthy и falsy для обозначения ситуации, когда
значение переменной заранее преобразуется в логическое «истинно» или «ложно»
соответственно. Емкий перевод обнаружился на Habrahabr и звучит как «значения
правдочки и кривдочки». — Примеч. пер.
Использование JSLint для выявления возможных проблем
Вставим наш код в JSLint и посмотрим, что нам скажут. Сервис должен ответить
так, как показано на рис. 4.10.
Сделав это и перезапустив проверку, мы увидим, что первые две ошибки исчез-
ли. Следующая — ошибка назначения, о которой мы уже говорили. Исправив ее,
перейдем к двум последним.
Последние две ошибки связаны с глобальной переменной console, которая была
использована до своего объявления. Мы не вправе просто добавить слово var перед
этой переменной, поскольку не создаем ее. Но можно перейти к опциям JSLint,
включить глобальную опцию console, alert... после чего эти ошибки исчезнут.
Как и CSS Lint, JSLint эмулирует присутствие рядом с вами опытного настав-
ника, профессионала в JavaScript, который проверяет, как вы пишете код. Порой
его замечания будут не совсем понятными и вам придется какое-то время разби-
раться, в чем состоит ошибка и как ее исправить (отличная стартовая точка для
этой работы — Google), но все же оно того стоит, и постепенно вы научитесь писать
код JavaScript более эффективно.
Глава 4. Интерактивность
$PD]HULI¿F
А сейчас рассмотрим все наши идеи на примере. Работа с приложением Amazeriffic
началась с определения цели — приложение должно выполнять функции списка
задач. Для ее достижения построим интерфейс, состоящий из трех вкладок. Первая
будет отображать список задач, начиная с самых новых (рис. 4.11).
Поскольку мы уже много работали с 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;
});
};
!$&
Надеюсь, что какое-то время вы размышляли над решением, прежде чем прочесть
его! Пришло ли вам в голову применить функцию, где в качестве аргумента ис-
пользуется номер вкладки? Если да — отлично! Если нет, все хорошо — скоро вы
с этим разберетесь. Нужно всего лишь немного практики.
Это нормально, если ваше решение отличается от моего. Самое главное — по
старайтесь разобраться в принципе работы этого решения:
Глава 4. Интерактивность
!$&
Итак, мы немного сократили количество строк в коде и, более того, сделали это
способом, позволяющим избежать ошибок. Но можно пойти еще дальше. Обрати-
те внимание на то, как мы пометили вкладки: с помощью цифр 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;
});
});
};
Как видите, есть три способа решения подобных задач. Могут быть, конечно,
и другие, но, думаю, два последних неплохи — они коротки, понятны (если, конеч-
но, вы умеете работать с циклами), а кроме того, с их помощью легко добавлять
дополнительные вкладки.
ß#
Теперь нужно решить другую проблему. Внутри вкладок элемент main .content
должен заполняться по-разному в зависимости от того, какая вкладка выбрана.
Это очень легко, если мы знаем, на каком потомке элемента .tabs находимся. На-
пример, если активен первый потомок элемента .tabs, мы делаем одно, если вто-
рой — что-то другое. Но на самом деле это не так-то просто, потому что внутри
обработчика событий мы имеем доступ только к элементу span, который является
потомком интересующего нас элемента. Древовидная диаграмма этой ситуации
изображена на рис. 4.14.
Оказывается, что в jQuery существует отличный способ выбрать предок объ-
екта jQuery — функция, которая так и называется — parent. Но затем нужно опре-
делить, с которым по счету потомком мы имеем дело. Для этого в jQuery есть
функция is, которая позволяет проверить селектор относительно текущего объ-
екта jQuery. Наверное, понять эту абстракцию нелегко, но на практике все это
вполне читаемо. Вот короткий пример:
Глава 4. Интерактивность
Рис. 4.14. Нам нужно знать индекс элемента span, на котором мы щелкнули
ı
Настройка содержимого вкладки требует несколько более сложной работы. Прежде
всего нужно создать сами списки задач в виде массива строковых элементов.
Для этого добавим переменную внутрь функции 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.
Ø$
#M4XHU\
Одна из причин популярности jQuery — существование крупного сообщества веб-
разработчиков, которые создают плагины, позволяющие добавлять на вашу страни-
цу невероятные эффекты. Я рекомендую посетить страницу http://plugins.jquery.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>
)L]]%X]]
Если вы подумываете о том, чтобы работать программистом, вам обязательно нуж-
но уметь решать задачку FizzBuzz. Насколько я знаю, эта задача стала популярной
после поста в блоге Джеффа Этвуда под названием «Почему программисты не
могут… программировать?» (Why Can’t Programmers… Program?). В посте Джефф
Больше теории и практики
ß’
Еще один часто встречающийся источник вопросов на собеседованиях — массивы.
Это легко объяснимо: определенно это самая часто встречающаяся структура дан-
ных в компьютерных программах, в той или иной форме они присутствуют в каж-
дом языке программирования. Попробуйте ответить на все вопросы в этом разделе
с помощью материала данной главы. Начнем с простого вопроса с несколькими
вариантами ответа: напишите функцию, которая принимает массив чисел в качес-
тве аргумента и возвращает их сумму.
Самое простое решение будет выглядеть вот так:
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. Интерактивность
Цикл 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!
После того как вам удалось это сделать, напишите функцию под названием
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: "пик"}
Переменные внутри объекта могут быть любого типа, включая строки, ко-
торые мы видели во всех предыдущих примерах, а также массивы или даже
объекты:
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);
//=> "Грейси"
В этом примере у Джона нет собаки. Мы можем использовать null также для
того, чтобы обозначить окончание работы с этим объектом:
// назначаем currentPerson объекту g
var currentPerson = g;
// ... выполняем какие-то действия с currentPerson
// устанавливаем currentPerson на null
currentPerson = null;
$&
В наши дни почти невозможно создать веб-приложение, не затрагивая других уже
существующих приложений. Например, вы хотите реализовать возможность авто-
ризации ваших пользователей через аккаунт 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);
//=> "червей"
$-$;
AJAX расшифровывается как Asynchronous JavaScript And XML (асинхронный
JavaScript и XML, что, как я уже говорил, не совсем верно). Общий формат обмена
Обмен информацией между компьютерами
-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);
’
На момент появления JavaScript был предназначен для работы в браузере. Это озна-
чало, что по соображениям безопасности доступ к локальным файлам, хранящимся
Глава 5. Мост
Чтобы запустить Chrome без этого ограничения, следует сначала закрыть его.
Затем, если вы работаете в Mac, откройте командную строку и введите следующую
команду:
hostname $ open -a Google\ Chrome --args --allow-file-access-from-files
’JHW-621
Если вам удалось успешно отключить ограничение по безопасности в Chrome
между сайтами, вы легко получите доступ к локальному файлу JSON из нашей
программы. Чтобы сделать это, надо использовать функцию в jQuery, которая
называется getJSON. Как и большинство примеров JavaScript, этот запрос jQuery
будет асинхронным, поэтому нужно добавить обратный вызов:
var main = function () {
"use strict";
// getJSON сразу интерпретирует JSON, поэтому
// нет необходимости вызывать JSON.parse
$.getJSON("cards/aceOfSpades.json", function (card) {
Обмен информацией между компьютерами
Если все было сделано верно, то, открыв страницу в 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("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);
$"
Я понимаю, что этот пример не слишком впечатляющий. На самом деле вы, навер-
ное, подумали, что было бы гораздо проще определить эти объекты прямо внутри
кода. Зачем же понадобился отдельный файл?
Что ж, представьте, что данные поступают к вам с отдельного компьютера. В этом
случае очень пригодится возможность доступа к ним из нашего кода. Фактически
изначально мы можем даже не знать, как выглядит объект! Например, представьте,
что мы хотим получить самые свежие изображения собак, загруженные на 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>
Это выглядит несколько сложнее, чем примеры, виденные вами ранее, но ос-
новная структура и формат остаются теми же самыми. Он начинается с основной
информации о запросе, а затем выводит свойство 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);
});
});
$
$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);
});
});
описание задачи, а сейчас это массив объектов. Если нам нужно, чтобы код работал
как раньше, можно создать массив старого типа из нового с помощью функции 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
œ
Начнем с добавления вкладки Теги в пользовательский интерфейс. Поскольку мы
сделали код (относительно) гибким, это будет не очень сложно. Начнем с открытия
файла 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. Мост
На этой вкладке все имеющиеся теги должны быть перечислены как заголовки,
а после них добавлены все описания задач, подходящие под эту категорию. Это
значит, что некоторые задачи могут появляться в некоторых местах.
Проблема здесь, однако, еще и в том, что способ сохранения объекта JSON не-
сколько затрудняет задачу. Было бы лучше, если бы toDoObjects хранились в фор-
мате, организованном с помощью тегов:
[
{
"name": "покупки",
"toDos": ["Купить продукты"]
},
{
"name": "рутина",
"toDos": ["Купить продукты", "Вывести Грейси на прогулку в парк"]
},
{
"name": "писательство",
"toDos": ["Сделать несколько новых задач", "Закончить писать книгу"]
},
{
Добавление теговой функциональности в Amazeriffic
"name": "работа",
"toDos": ["Сделать несколько новых задач", "Подготовиться к лекции
в понедельник","Ответить на электронные письма", "Закончить писать книгу"]
},
{
"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. Мост
ø##
ı#
Мы можем проверить результаты нашей работы в консоли 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. Мост
Мое решение относительно просто объяснить в два этапа. Сначала я создаю массив,
который содержит все возможные теги, с помощью перебора изначальной струк-
туры с помощью 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
toDo.tags.forEach(function (tag) {
// убеждаемся, что этого тега
// еще нет в массиве
if (tags.indexOf(tag) === -1) {
tags.push(tag);
}
});
});
console.log(tags);
};
Сейчас, когда у нас есть функция и она корректно работает, можем вставить ее
в функцию main кода приложения, и он будет работать! Как я уже говорил, эта
проблема может быть решена различными способами, так что будет здорово ис-
пробовать какие-нибудь еще.
œ$#
#
Итак, мы добились того, чтобы элементы списка задач организовывались и ото
бражались с помощью тегов, но как мы будем добавлять теги к новым элементам,
которые вводим с помощью вкладки Добавить? Это требует модификации кода,
который отвечает за отображение интерфейса вкладки. Я предлагаю преобразовать
его так, чтобы интерфейс выглядел как на рис. 5.3.
Глава 5. Мост
Как вы видите, там находятся два поля ввода. Во втором из них пользователь
может ввести список тегов, разделенных запятыми, которые затем ассоциируются
с добавляемым объектом.
Если вы до конца разобрались с примером из предыдущей главы, то ваш код,
вероятно, выглядит примерно так:
} 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);
}
В этой главе мы изучили основы трех очень важных тем: объекты JavaScript, JSON
и AJAX. Я представил вам объекты JavaScript как способ хранения связанных
данных в программе в виде единого целого. Объекты создаются с использованием
фигурных скобок, а их свойства добавляются и связываются с ними с помощью
оператора «точка» (.).
Вы можете рассматривать JSON как строковое представление объектов JavaScript,
которые могут быть обработаны на любом языке программирования. JSON исполь-
зуется для передачи данных между компьютерными программами — в нашем случае
между браузером (клиентом) и сервером. Кроме того, многие веб-сервисы (включая
Flickr, Twitter и Facebook) предлагают API — интерфейсы программирования
Глава 5. Мост
Ø$
ø )OLFNU
В этой задаче вам нужно создать простое приложение, которое позволяет пользо-
вателю ввести параметры поиска, а затем отображает серию изображений из Flickr,
содержащих этот тег. Изображения должны появляться и исчезать по очереди.
Для этого нам понадобится функция setTimeout, с которой мы познакомились
в главе 4. Эта функция позволяет нам запланировать событие, которое должно
произойти спустя определенное время. Допустим, например, что мы хотим вывести
сообщение «Нello, World!» через 5 секунд. Можем сделать это следующим обра-
зом:
// вывод "hello, world!" через 5 секунд
setTimeout(function () {
console.log("hello, world!");
}, 5000);
Обратите внимание на то, что аргументы этой функции указаны не так, как мы
привыкли (обычно обратный вызов идет последним), а наоборот. Это особенность,
которую нужно просто запомнить. Обратите внимание также на то, что второй
аргумент представляет собой количество миллисекунд, которое необходимо выждать
перед вызовом функции.
А сейчас представим себе, что нужно просто отобразить и скрыть массив сооб-
щений:
var messages = ["первое сообщение", "второе сообщение", "третье", "четвертое"];
На самом деле, скорее всего, не нужно добавлять этот эффект на реальные веб-страни-
цы. Это современный эквивалент тега <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(["двойка","четверка","двойка","король","восьмерка"], "двойка", 3);
//=> false
}
});
// в результате получаем true, если пара найдена,
// и false, если нет
return result;
};
$3,
Теперь, когда вы убедились, как легко получать данные из Flickr и работать с ними,
может быть интересно получить данные из других API. Оказывается, очень многие
API позволяют получать различные данные точно так же, как Flickr.
Портал Programmable Web содержит целый список API. Для сайта, использу-
ющего функцию jQuery getJSON, необходима поддержка технологии под названием
JSONP. Вы можете получить список API, содержащих JSONP, а также почитать
об этой технологии в «Википедии».
Вам придется почитать документацию для определенных API, чтобы понять,
как форматировать URL запроса, но упражнения с различными API — это отличный
способ попрактиковаться в работе с AJAX.
ø
ı’
Настройка рабочего окружения с поддержкой приложений, управляемых базами
данных, может быть непростой задачей, и описание этой процедуры для Windows,
Mac OS и Linux выходит за рамки книги. Для упрощения процесса я создал набор
сценариев, которые помогут вам приступить к работе с использованием VirtualBox
и Vagrant достаточно быстро.
Vagrant — это инструмент для настройки рабочего окружения на виртуальной
машине. Вы можете думать о виртуальных машинах как об отдельных компьютерах,
работающих полностью в пределах вашего компьютера. Мы поговорим о них по
дробнее в разделе «Ментальные модели». А сейчас просто запомните, что мы будем
использовать Vagrant и VirtualBox для настройки виртуального рабочего окруже-
ния на серверной стороне. Это окружение будет включать в себя большинство
инструментов, которые понадобятся нам в оставшейся части книги.
В общем-то, часть этого процесса связана в большей степени с удобством: хотя
установка рабочего окружения Node.js на вашей локальной машине совсем не
сложна (и в конце главы я буду рекомендовать вам сделать это самостоятельно),
добавление набора инструментов для сервера, включая MongoDB и Redis, доволь-
но нетривиально и требует много времени и терпения.
Другая причина, почему я так поступаю, — стабильность. Поскольку я сам на-
писал сценарии, я получу несколько больше контроля над установленными версия
ми Node.js, Redis и MongoDB. Более того, использование Vagrant позволит иметь
одно и то же рабочее окружение независимо от того, работаете ли вы в Windows,
Глава 6. Сервер
ß9LUWXDO%R[
9DJUDQW
Прежде всего вы должны установить последнюю версию VirtualBox. На момент
написания книги это версия 4.2.18. Вы можете зайти на http://www.virtualbox.org
(рис. 6.1), щелкнуть на ссылке Downloads в левой части страницы и скачать соот-
ветствующую версию для вашей операционной системы. Загрузка, установка и на-
стройка будут зависеть от операционной системы, поэтому внимательно следуйте
инструкциям.
ø$ #
Если все получилось, вы готовы к скачиванию хранилища 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
1RGHMV
После того как вы подключились, можете работать в командной строке виртуаль-
ного компьютера точно так же, как в командной строке на локальном. Например,
вы можете вывести на экран содержимое какой-либо папки на виртуальной маши-
не тем же способом, каким пользовались на локальной, — с помощью команды ls:
vagrant $ ls
app postinstall.sh
Здесь вы должны увидеть файл server.js. Проверим, что все работает правиль-
но, начав с команды node:
vagrant $ node server.js
Server listening on port 3000
Глава 6. Сервер
1
Сообщение: «Мягкое завершение работы виртуальной машины…». — Примеч. пер.
Ментальные модели
$#
В данном разделе обсудим различные способы понимания природы клиентов,
серверов, хостов и гостей.
# #
В области компьютерных сетей о программах обычно говорят либо как о серверных,
либо как о клиентских. Традиционно серверная программа рассматривает какой-либо
ресурс как сеть, в которую могут получить доступ несколько клиентских программ.
Например, кому-либо может понадобиться переместить файлы с удаленного компью-
тера на локальный. FTP-сервер — это программа, которая предоставляет протокол
передачи файлов (File Transfer Protocol), позволяющий пользователям сделать это.
А FTP-клиент — это программа, которая устанавливает связь с FTP-сервером и пе-
редает файлы. Самая общая модель «клиент — сервер» представлена на рис. 6.6.
#
Обычно HTTP-сервер запускается на удаленной машине (позднее в этой книге мы
проделаем это самостоятельно). Из-за этого у разработчиков возникают некоторые
проблемы — если код запускается на удаленном сервере, приходится или редакти-
ровать код на удаленном сервере, а затем перезагружать его, или редактировать код
локально и отправлять его на сервер всякий раз, когда нужно попробовать измене-
ния. Это может быть нерационально.
Мы можем обойти эту проблему, запустив сервер на своей машине локально.
Фактически мы делаем таким образом шаг вперед. Вместо того чтобы локально
запустить программу сервера и все программное обеспечение, мы создаем внутри
своего компьютера виртуальную машину, на которой запускается сервер. Для че-
ловека это выглядит так же, как удаленная машина (в том смысле, что мы подклю-
чаемся к ней через SSH, тем же способом, что и к удаленной машине). Но посколь-
ку машина работает внутри нашего компьютера, можем просто предоставить
локальной машине права доступа к папке с рабочими файлами и таким образом
редактировать их.
Локальный компьютер, на котором запущена виртуальная машина, является
хостом (хозяином). Виртуальная машина является гостем. В оставшейся части
главы они оба будут запущены, и я буду четко разделять их.
#
Эта абстракция может привести к недопониманию, которое будет прояснено позже.
Прежде всего поговорим о приложениях, которые будут запущены во время про-
цесса разработки, и об окнах, с которыми они будут связаны.
Браузер. Вы будете использовать Chrome для тестирования своего приложения.
Текстовый редактор. Если вы до сих пор делали все правильно, это, скорее все-
го, будет Sublime. Он будет редактировать файлы, находящиеся в папке с раз-
деленными правами доступа как на хостовой, так и на гостевой машине.
Git. Командная строка Git должна быть открыта всегда. Используйте Git с ма-
шины-хоста. Если вы работаете в Mac OS или Linux, это будет командное окно,
если же в Windows — командная строка git-bash.
Vagrant. Скорее всего, вы будете взаимодействовать с Vagrant через то же окно,
которое используете для работы с Git. Это значит, что вы можете ввести vagrant
up или vagrant halt в том же самом месте, где набираете git commit.
Привет, HTTP!
+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. Сервер
([SUHVV
Уф! Не слишком ли много для маленькой программы? А ведь это еще не все — сер-
вер HTTP требует хороших навыков и знаний для корректного написания кода.
К счастью, нет необходимости заботиться о деталях, так как мы импортировали
код через модуль http Node.js. Модуль — это просто какое-то количество кода,
которое мы можем использовать без необходимости полностью понимать его ра-
боту — нужно знать лишь API, который предоставляет код. Первая строка нашего
кода — оператор require, который импортирует модуль http и хранит его в пере-
менной http.
Этот серверный модуль http интересен, если нам нужен самый примитивный,
базовый HTTP-сервер, который просто принимает запросы клиента и отвечает на
них. Поскольку мы хотим начать с отправки файлов HTML или CSS с сервера, все
становится несколько сложнее. Мы можем создать более сложный сервер поверх
базового, который предоставляет нам Node.js, но, к счастью, решение уже создано
до нас! Модуль Express создает поверх ядра модуля http надстройку, которая об-
рабатывает большинство сложных вещей, о которых нам не нужно заботиться
самостоятельно, в частности, обслуживает статичный HTML, CSS и файлы JavaScript
на клиентской стороне.
Модули и Express
ß([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 — это должно помочь.
#([SUHVV
Сейчас, когда модуль Express установлен, мы можем создать простой сервер Express.
Создадим внутри каталога Express файл, назовем его server.js и добавим следующее
содержание:
Глава 6. Сервер
Если остановить сервер (нажав Ctrl+C) и запустить его снова, мы сможем полу-
чить доступ к localhost:3000, как уже было до этого! Express обрабатывает марш-
руты самостоятельно — мы просто говорим ему, что должно быть сделано в случае
запроса определенного маршрута.
’
Вы уже видели, как можно отправлять информацию в браузер от сервера. Но
что, если мы хотим послать нечто вроде базовой HTML-страницы? Задание
Модули и Express
Если вы попробуете запустить этот пример в Windows вместо своей виртуальной маши-
ны, то, скорее всего, столкнетесь с проблемой, вызванной именами файлов. Для про-
стоты изложения я отказался от использования базового модуля path, который придал
бы папке свойство кросплатформенной совместимости. В то же время я использовал его
в коде в хранилище GitHub, поэтому, если у вас возникнут какие-то трудности, ознакомь-
тесь с хранящимися там примерами.
Здесь легко можно запутаться — если у нас есть маршрут с тем же названием, что и файл
в папке client, как же ответит Express? Сначала он ищет ответ в клиентской папке и, если
найдет совпадение, даже не будет просматривать маршруты. Поэтому нельзя создавать
файлы и маршруты с одним именем — это не приведет ни к чему хорошему практичес-
ки никогда.
!#
В дальнейшем будем создавать все приложения, следуя этому общему шаблону.
Браузерный код будет храниться в каталоге 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": "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
«Потрясающе», «круто», «супер», «великолепно», «классно». В данном примере
использование кириллических символов требует изменения кодировки для правильного
их отображения в командной строке. — Примеч. пер.
Считаем твиты
%$"
Посмотрим на код еще раз. Вы увидите несколько знакомых элементов. Во-первых,
мы объявили три переменные и импортировали модуль 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. Сервер
’([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"));
Считаем твиты
ı
Напишем основу клиентского приложения, которая, как обычно, будет включать
в себя файлы 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. Сервер
Почему это сделано именно так? Потому, что такой способ реализации позво-
ляет нам проделать нечто похожее на недавние операции с кодом на серверной
стороне — использовать функцию setInterval, чтобы обновлять страницу динами-
чески:
var main = function () {
"use strict";
var insertCountsIntoDOM = function (counts) {
// здесь код для управления элементами DOM
};
// проверка значения counts каждые 5 секунд
// и вставка обновленных значений в DOM
setInterval(function () {
$.getJSON("counts.json", insertCountsIntoDOM);
}, 5000);
}
$(document).ready(main);
ø’$PD]HULI¿F
Если вы как следует поработали с примером из предыдущей главы, то вполне
справитесь с организацией работы приложения Amazeriffic так, чтобы оно обраба-
тывало список задач, передаваемый файлом JSON, а не жестко прописанный в про-
граммном коде. На этом примере я предлагаю вам начать с работы с хранилищем
Git, чтобы отслеживать изменения, появляющиеся по ходу работы.
ı
Начнем с создания папки внутри каталога Chapter5/app, которую назовем Amazeriffic.
Напомню, что не имеет значения, сделаем мы это на гостевой или на хостовой маши-
не. Внутри папки создадим еще одну для хранения клиентского кода. Назовем ее
client и скопируем туда содержимое предыдущего примера с Amazeriffic из главы 5.
Создание сервера для Amazeriffic
ø!*LW
Затем заложим основу хранилища Git, отправив туда скопированные файлы. Пом-
ните, что взаимодействовать с Git нужно с хостовой машины. С этого момента я пре-
доставляю вам выбирать подходящие моменты для выполнения коммитов:
hostname $ git init
Initialized empty Git repository ...
ø
Сейчас наше приложение работает отлично, если исходить из предположения, что
пользователь никогда не переходит в другой браузер или на другой компьютер и ни-
когда не перезагружает страницу. К сожалению, если пользователь предпримет одно
из этих действий, он потеряет все созданные задачи и вернется к исходной точке.
Мы решаем эту проблему, отправляя список задач на хранение на сервере и пе-
ренастраивая клиент для обработки данных с сервера. Для начала создадим список
задач в переменной на сервере и настроим маршрут 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. Сервер
ł!
До этого момента в программировании на клиентской стороне мы ограничивались
получением данных с сервера. Это значит, что наши приложения поддерживали
только одностороннюю коммуникацию между сервером и клиентом. Для обеспе-
чения этой коммуникации мы пользовались функцией jQuery под названием
getJSON. Оказывается, в jQuery существует и такая функция, которая позволяет
легко отправлять на сервер объекты JSON, но для этого нужно модифицировать
серверную сторону и подготовить ее к приему данных.
Процесс отправки данных с клиента на сервер через протокол HTTP называет-
ся post. Начнем с настройки маршрута post на сервере Express. Следующий пример
настроит маршрут post, который просто выводит строку в терминале сервера:
app.post("/todos", function (req, res) {
console.log("Данные были отправлены на сервер!");
// простой объект отправлен обратно
res.json({"message":"Вы размещаетесь на сервере!"});
});
Ø$
$’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
это не обязательно.
Этот код, конечно, будет отлично работать в браузере, но в нем пропущена точ-
ка с запятой после определения функции 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"];
В общем случае стоит применить способ с точкой, так как большинство про-
граммистов 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
}
Может понадобиться включить еще несколько функций для проверки того, что
раздача действительно является покерной, и даже специальную функцию для
возврата объекта, указанного API. Таким образом, обратный вызов маршрута, по
сути, уместится в одной строке:
app.post("/hand", function (req, res) {
var result = poker.getHand(req.body.hand);
res.json(result);
});
64/
64/
Если вы изучали базы данных, то, наверное, вам знакома аббревиатура SQL (иногда
произносится как sequel1), что значит Structured Query Language (структурирован-
ный язык запросов). Это язык, используемый для запросов к базе, в которой данные
хранятся в реляционном формате. Реляционные базы данных хранят данные в ячей-
ках таблиц, в них таблицы легко связываются с другими таблицами с помощью
перекрестных ссылок. Например, в реляционной базе данных может храниться
1
Читается «сиквел», на русском языке тоже так говорят, но редко. — Примеч. пер.
Глава 7. Хранение данных
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. Хранение данных
ß’5HGLVSDFNDJHMVRQ
К этому моменту мы уже знаем, как создать ключ для каждого слова, а затем ин-
терактивно наращивать его значение, но хотелось бы проделать это с помощью
программирования Node.js из наших приложений.
{
"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"
}
}
Как видите, таким образом указаны несколько деталей о нашем проекте, вклю-
чая его взаимозависимости (поскольку для 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;
Если вдруг вы захотите очистить хранилище данных Redis, можете сделать это в любой
момент. Нужно набрать команду flushall в командной строке redis-cli.
ß$’
!5HGLV
Сейчас наши данные сохраняются в течение работы программы, но, перезапуская
сервер, мы все еще обнуляем объект count. Для окончательного решения этой про-
блемы нужно, чтобы счетчик твитов устанавливал в качестве начального значения
данные, которые сохранены в Redis. Мы можем использовать команду get, чтобы
получить их перед тем, как запустим поток. Но, как и многое в Node, функция get
является асинхронной, поэтому работать с ней надо осторожно:
Redis
Вы заметили, что обратный вызов для 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 с помощью 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" : "треф"
}
]
Вы видите, что коллекция cards не существовала перед тем, как в нее сохранили
объект. Мы можем использовать для коллекций функцию find, не требующую
аргументов, чтобы увидеть, какие документы там хранятся:
> db.cards.find();
{ "_id" : ObjectId("526ddeea7ba2be67c95558d8"),"rank":"туз","suit":"треф" }
В дополнение к свойствам rank (номинал) и suit (масть) карта имеет также _id,
связанный с ней. В большинстве случаев каждому документу в коллекции MongoDB
присваивается идентификационный номер.
MongoDB
{ "_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, а также доступных типах запросов.
#0RQJRRVH
Mongoose — это модуль Node.js, который служит для двух основных целей. Прежде
всего он работает как клиент для MongoDB аналогично тому, как модуль node-redis
MongoDB
работает в качестве клиента Redis. Но кроме этого, Mongoose работает как инструмент
для моделирования данных, позволяющий представлять документы как объекты в на-
ших программах. В этом разделе мы изучим основы моделирования данных и ис-
пользуем Mongoose для создания модели данных для задач Amazeriffic.
Схемы могут быть более сложными. Например, мы можем построить схему для
записей в блоге, которая будет содержать даты и комментарии. В этом примере
атрибут comments (комментарии) будет представлять собой массив строк вместо
единичной строки:
var BlogPostSchema = mongoose.Schema({
title: String,
body : String,
date : Date,
comments : [ String ]
});
Как же модель может нам помочь? Имея модель, мы можем создать объект соот-
ветствующего ей типа очень просто, используя оператор JavaScript new. Например,
такая строка кода создает туз пик и сохраняет его в переменной под названием с1:
var c1 = new Card({"rank":"туз", "suit":"пик"})
’$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');
Ø$
$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 дней, в течение которых вы можете попробовать загрузить
туда некоторые из примеров-приложений из этой книги.
Подготовка приложений к развертыванию в Сети
ł’
Чтобы начать, вам нужно создать учетную запись на http://www.cloudfoundry.com. Вы
можете получить 60 дней бесплатного использования, щелкнув на ссылке в верхнем
правом углу и набрав свой адрес электронной почты. В ответ получите информацию
о том, как настроить учетную запись.
#&
ø
Чтобы развернуть приложение, вам понадобится инструмент, называемый cf. Ин-
сталляция его на локальную машину требует предварительной установки языка
Ruby и пакет-менеджера RubyGems. Я включил приложение cf в проект node-dev-
bootstrap, так что вы можете начать развертывание прямо с гостевой машины без
всяких дополнительных установок!
Чтобы начать, создайте папку Chapter8 клонированием проекта node-dev-bootstrap,
как вы уже делали в предыдущих двух главах. Покончив с этим, запустите гостевую
машину и подключитесь к ней по SSH.
1
На момент перевода с адреса, указанного в предыдущем абзаце, идет перенаправление
на тот, что вы видите на скриншоте в URL. Однако этот сервис предлагает примерно те
же функции, что и Cloud Foundry, поэтому вы можете и далее пользоваться инструкциями
из книги. — Примеч. пер.
Глава 8. Платформа
ł#’
К этому моменту мы уже можем запустить виртуальную машину и подключиться
к ней через браузер, как и раньше. А сейчас, создав файл package.json и настроив
приложение на слушание по корректному порту, мы также готовы загрузить его на
Cloud Foundry.
Как я упоминал ранее, это потребует использования программы cf, которая уже
установлена на гостевой машине. Чтобы начать, перейдите на гостевой машине
в папку, где находится файл server.js.
Развертывание приложения
Имя вашего приложения должно быть уникальным среди всех имен в этом домене. Это
значит, что, если вы выберете имя наподобие 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, которую вы можете использовать для запущенных
приложений, чтобы просмотреть программные логи:
Получение информации о приложениях
’
Вы легко можете послать новейшую версию приложения в Cloud Foundry, развер-
нув его заново. Модифицируем server.json так, чтобы он возвращал несколько
больше информации:
Глава 8. Платформа
ß
&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 к приложению
Обратите внимание: мы сказали 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
’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 точно так же, как в предыдущем примере. Одно незначительное изменение
вызвано необходимостью настроить сервис 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. Приложение
"name":"Теги",
"content":function () {
// создание $content для Теги
return $content;
}
});
// Создаем вкладку Добавить
tabs.push({
"name":"Добавить",
"content":function () {
// создание $content для Добавить
return $content;
}
});
};
Œ$-$;’#
Другая проблема приложения заключается в том, что, если кто-либо посетит его
из другого браузера и добавит какую-нибудь задачу в наш список, мы не увидим
этот новый элемент, если щелкнем на другой вкладке. Для этого нужно будет пе-
резагрузить всю страницу.
Глава 9. Приложение
Обратите внимание на то, что таким образом меняется поведение функций, так
как теперь мы собираемся делать асинхронный запрос внутри вызова функции.
Это значит, что код теперь не будет корректно возвращать содержимое при вызове,
так как мы должны дождаться завершения вызова AJAX:
$content = tab.content();
$("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, то есть буквально «ад обратного вызова». Но омут как нечто
крутящееся для данной ситуации подходит больше, а черти есть и тут, и там. — При
меч. пер.