Учебник Express часть 6: Работа с формами - Изучение веб-разработки

Учебник Express часть 6: Работа с формами
=========================================

* Назад
* Обзор: Express Nodejs
* Далее  (en-US)

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

| Предварительные знания: | Завершите изучение предыдущих тем учебника, включая Учебник Express Часть 5: Отображение данных библиотеки |
| --- | --- |
| Цель: | Понять, как писать формы для получения данных от пользователей и обновлять базу данных с этими данными. |

Обзор
-----

HTML форма - это группа из одного или нескольких полей / виджетов на веб-странице, которая может использоваться для сбора информации от пользователей для отправки на сервер. Формы представляют собой гибкий механизм для сбора данных, вводимых пользователем, поскольку существуют подходящие входные данные форм, доступные для ввода различных типов данных-текстовые поля, флажки, переключатели, средства выбора даты и т. д. Формы также являются относительно безопасным способом обмена данными с сервером, поскольку они позволяют отправлять данные в запросах `POST` с защитой от подделки межсайтовых запросов.

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

В этом уроке мы покажем вам, как вышеуказанные операции могут быть выполнены в *Express*. По пути мы расширим веб-сайт *LocalLibrary*, чтобы пользователи могли создавать, редактировать и удалять элементы из библиотеки.

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

### HTML Forms

Первый краткий обзор HTML Forms. Рассмотрим простую HTML-форму с одним текстовым полем для ввода имени некоторой "команды" и связанной с ней меткой:

![Simple name field example in HTML form](/ru/docs/Learn/Server-side/Express_Nodejs/forms/form_example_name_field.png)

Определённые в HTML формы собираются внутри тэга `<form>...</form>`, содержащего хотя ы один элемент `input` с `type="submit"`.

```html
<form action="/team\_name\_url/" method="post">
  <label for="team\_name">Enter name: </label>
  <input
 id="team\_name"
 type="text"
 name="name\_field"
 value="Default name for team." />
  <input type="submit" value="OK" />
</form>
```

Хотя здесь мы включили только одно (текстовое) поле для ввода имени команды, форма может содержать любое количество других элементов ввода и связанных с ними меток. Атрибут `type` определяет какой из виджетов будет выбран для отображения поля. Атрибуты `name` и `id` идентифицируют поле в JavaScript/CSS/HTML, а `value` определяет его первоначальное значение. Связанная с полем метка, задаётся с помощью тега `label` (располагается строкой выше и содержит в себе подпись "Enter name"). Связь метки и поля ввода устанавливается при помощи атрибута `for`, в котором указывается значение идентификатора поля (`input` `id`).

Input `submit` будет отображаться в виде кнопки (по умолчанию) - он может быть нажат пользователем, чтобы загрузить данные, содержащиеся в других входных элементов на сервер (в данном случае, только team\_name). Атрибуты формы определяют метод HTTP, используемый для отправки данных, и назначение данных на сервере (action):

* `action`: ресурс/URL-адрес, по которому данные должны отправляться на обработку при отправке формы. Если это не установлено (или установлено в пустую строку), то форма будет отправлена назад к URL текущей страницы.
* `method`: Метод HTTP, используемый для отправки данных: `POST` or `GET`.
    + Метод `POST` должен всегда использоваться, если данные собираются привести к изменению базы данных сервера, потому что это может быть сделано более устойчивым к атакам запроса подделки межсайтового.
    + Метод `GET` следует использовать только для форм, которые не изменяют пользовательские данные (например, форма поиска). Рекомендуется, когда вы хотите, чтобы иметь возможность делать закладки или поделиться URL.
### Процесс обработки формы

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

Блок-схема процесса обработки запросов формы показана ниже, начиная с запроса страницы, содержащей форму (показана зелёным цветом):

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/web_server_form_handling.png)

Как показано на диаграмме выше, основные действия, которые необходимо выполнить коду обработки форм:

1. Отображение формы по умолчанию при первом запросе пользователем.
    * Форма может содержать пустые поля (например, если вы создаёте новую запись), или она может быть предварительно заполнена начальными значениями (например, если вы изменяете запись или имеете полезные начальные значения по умолчанию).
2. Получение данных, отправленных пользователем, обычно в запросе HTTP `POST`.
3. Валидация и очистка данных.
4. Если какие-либо данные недопустимы, повторно отобразите форму—на этот раз с заполненными пользователем значениями и сообщениями об ошибках для проблемных полей
5. Если все данные верны, выполнить требуемые действия (например, сохранить данные в базе данных, отправьте уведомление по электронной почте, возвращающие результат поиска, загрузить файл и т. д.)
6. После завершения всех действий перенаправьте пользователя на другую страницу.

Часто код обработки формы реализуется с помощью `GET` route для начального отображения формы и `POST` route к тому же пути для обработки проверки и обработки данных формы. Это подход, который будет использоваться в этом уроке!

Сам Express не предоставляет какой-либо конкретной поддержки для операций обработки форм, но он может использовать промежуточное программное обеспечение для обработки `POST` и `GET` параметров из формы, а также для проверки/очистки их значений.

### Валидация и обработка

Перед сохранением данных формы их необходимо проверить и очистить:

* Проверка проверяет, что введённые значения являются подходящими для каждого поля (расположены в правильном диапазоне, формат и т. д.) и что значения были предоставлены для всех обязательных полей.
* Очистка удаляет / заменяет символы в данных, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер.

В этом уроке мы будем использовать популярный модуль express-validator для проверки и очистки данных формы.

#### Установка

Установите модуль, выполнив следующую команду в корне проекта

```bash
npm install express-validator
```

#### Использование express-validator

**Примечание:** express-validator руководство на Github предоставляет хороший обзор API. Мы рекомендуем вам прочитать это, чтобы получить представление о всех его возможностях (включая создание пользовательских валидаторов). Ниже мы рассмотрим только подмножество, которое полезно для *LocalLibrary*.

Для того, чтобы использовать валидатор в наших контроллерах, мы должны требовать функции, которые мы хотим использовать из модулей **'express-validator/check**' и **'express-validator/filter**', как показано ниже:

```js
const { body, validationResult } = require("express-validator/check");
const { sanitizeBody } = require("express-validator/filter");
```

Есть много доступных функций, позволяющих проверять и очищать данные из параметров запроса, тела, заголовков, файлов cookie и т. д., или все сразу. Для этого урока мы будем использовать `body`, `sanitizeBody`, and `validationResult` (как "требуется" выше).

Функции определяются следующим образом:

* `body(fields[, message])`: Задаёт набор полей в теле запроса (параметр `POST`) для проверки, а также необязательное сообщение об ошибке, которое может отображаться в случае сбоя тестов. Критерии проверки последовательно связаны с методом `body()`. Например, первая проверка ниже проверяет, что поле" имя "не пустое и задаёт сообщение об ошибке" пустое имя", если оно не пустое. Второй тест проверяет, что поле age является допустимой датой, и с помощью optional() указывает, что пустые и пустые строки не пройдут проверку.

```js
body('name', 'Empty name').isLength({ min: 1 }),
body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
```

Можно также последовательно подключить различные валидаторы и добавить сообщения, отображаемые при выполнении предыдущих валидаторов.
*
```js
body('name').isLength({ min: 1 }).trim().withMessage('Name empty.')
    .isAlpha().withMessage('Name must be alphabet letters.'),
```

**Примечание:** Вы также можете добавить встроенные средства очистки, такие как `trim()`, как показано выше. Однако средства очистки, применяемые здесь, применяются только к шагу проверки. Если требуется очистить конечный результат, необходимо использовать отдельный метод очистки, как показано ниже.
* `sanitizeBody(fields)`: Задаёт поле тела для очистки. затем операции очистки последовательно соединяются с этим методом. Например, операция очистки `escape()`, описанная ниже, удаляет символы HTML из переменной name, которые могут использоваться в атаках сценариев между сайтами JavaScript.

```js
sanitizeBody('name').trim().escape(),
sanitizeBody('date').toDate(),
```

* `validationResult(req)`: Запускает проверку, делая ошибки доступными в виде объекта результата проверки. Это вызывается в отдельном обратном вызове, как показано ниже:

```js
(req, res, next) => {
  // Extract the validation errors from a request.
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    // There are errors. Render form again with sanitized values/errors messages.
    // Error messages can be returned in an array using `errors.array()`.
  } else {
    // Data from form is valid.
  }
};
```

Мы используем метод `isEmpty()` результата проверки, чтобы проверить, были ли ошибки, и его метод array (), чтобы получить набор сообщений об ошибках. Дополнительные сведения см. в разделе API результатов проверки.

Цепочки проверки и очистки являются промежуточными запросами, которые должны быть переданы обработчику Express -маршрута (мы делаем это косвенно, через контроллер). При запуске промежуточного по каждый валидатор / средства очистки выполняется в указанном порядке..

Мы рассмотрим некоторые реальные примеры, когда мы реализуем *LocalLibrary* формы ниже.

### Дизайн формы

Многие модели в библиотеке связаны / зависимы—например, книга требует автора, а также может иметь один или несколько жанров. Это поднимает вопрос о том, как мы должны обрабатывать случай, когда пользователь хочет:

* Создайте объект, если связанные с ним объекты ещё не существуют (например, книга, в которой не определён объект автора).
* Удаление объекта, который все ещё используется другим объектом (например, удаление жанра, который все ещё используется книгой).

Для этого проекта мы упростили реализацию, объявив, что форма может быть только:

* Создайте объект, используя объекты, которые уже существуют (таким образом, пользователи должны будут создать все необходимые экземпляры автора и жанра, прежде чем пытаться создать любые объекты книги).
* Удалите объект, если на него не ссылаются другие объекты (например, вы не сможете удалить книгу, пока не будут удалены все связанные объекты BookInstance).

**Примечание:** Более" надёжная " реализация может позволить создавать зависимые объекты при создании нового объекта и удалять любой объект в любое время (например, путём удаления зависимых объектов или путём удаления ссылок на удалённый объект из базы данных).

### Маршруты

Чтобы реализовать наш код обработки форм, нам понадобятся два маршрута с одинаковым шаблоном URL. Первый (`GET`) маршрут используется для отображения новой пустой формы создания объекта. Второй маршрут (`POST`) используется для проверки введённых пользователем данных, а затем сохранения информации и перенаправления на страницу сведений (если данные верны) или повторного отображения формы с ошибками (если данные неверны).

Мы уже создали маршруты для всех страниц создания нашей модели в **/routes/catalog.js** (in a previous tutorial). Например, жанровые маршруты показаны ниже:

```js
// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get("/genre/create", genre_controller.genre_create_get);

// POST request for creating Genre.
router.post("/genre/create", genre_controller.genre_create_post);
```

Express формы — подразделы
--------------------------

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

1. Форма для создания Genre (en-US) — Определение нашей страницы для создания объектов `Genre`.
2. Форма для создания Author (en-US) — Определение страницы для создания объектов `Author`.
3. Форма для создания Book (en-US) — Определение страницы/формы для создания объектов `Book`.
4. Форма для создания BookInstance — Определение страницы/формы для создания объектов `BookInstance`.
5. Форма для удаления Author — Определение страницы для удаления объектов `Author`.
6. Форма для обновления Book — Определение страницы для обновления объектов `Book`.
Испытайте себя
--------------

Реализуйте страницы удаления для моделей `Book`, `BookInstance` и `Genre`, сделав на них ссылки с соответствующих страниц с подробными сведениями таким же образом, как это было нами сделано для страницы *Delete Author* («Удалить автора»). Эти страницы должны следовать тем же конструктивным принципам:

* Если на объект, указанный к удалению, существуют ссылки из других объектов, то должен быть выведен список этих других объектов вместе с сообщением о том, что соответствующая запись в БД не может быть удалена до тех пор, пока не будут удалены объекты из списка.
* Если на объект, указанный к удалению, нет ссылок из других объектов, то на визуальном представлении страницы должен быть выведен вопрос о подтверждении удаления этого объекта. Если пользователь подтвердит удаление нажатием кнопки **Delete**, запись должна быть удалена.

Несколько подсказок:

* Удаление объекта модели `Genre` очень похоже на удаление объекта модели `Author`, так как на каждый из этих объектов могут быть ссылки из объектов модели `Book` (таким образом, в обоих этих случаях вы можете удалить объект только тогда, когда будут удалены объекты модели `Book`, из которых есть ссылки на объект, указанный к удалению).
* Удаление объекта модели `Book` тоже похоже на удаление объекта модели `Author`, но в этом случае вам нужно будет проверять, нет ли объектов модели `BookInstance`, из которых есть ссылки на объект, указанный к удалению.
* Удаление объекта модели `BookInstance` самое простое из всех, потому что не существует объектов, из которых могут быть ссылки на объект модели `BookInstance`. В данном случае вы можете просто найти соответствующую запись и удалить её.

Реализуйте страницы обновления для моделей `BookInstance`, `Author` и `Genre`, сделав на них ссылки с соответствующих страниц с подробными сведениями таким же образом, как это было нами сделано для страницы *Update Book* («Обновить книгу»).

Несколько подсказок:

* Страница *Update Book* («Обновить книгу»), которую мы только что реализовали, является самой сложной в реализации! Те же приёмы можно использовать для страниц обновления объектов других моделей.
* Свойства объектов модели `Author` с датой рождения и датой смерти и свойство due\_back объектов модели `BookInstance` имеют неподходящий формат для ввода в поле формы с типом 'date' (оно требует ввод даты в виде строки в формате "YYYY-MM-DD"). Самый простой способ это обойти — создать новое виртуальное свойство в соответствующей модели для каждого реального свойства с датой, которое преобразует и возвратит дату в нужном формате, а затем использовать это виртуальное свойство в соответствующих шаблонах страниц.
* Если вы зашли в тупик, обратитесь к реализации страниц обновления в примере вот тут.
Резюмируем
----------

*Express*, Node.js и сторонние пакеты, полученные с помощью npm, обеспечивают всё, что вам потребуется для добавления форм на ваш сайт. В этой статье вы узнали, как создавать формы с помощью языка написания шаблонов *Pug*, как выполнять валидацию и очистку вводимых пользователем данных с помощью пакета *express-validator* и как добавлять, удалять и модифицировать записи в базе данных сайта.

Теперь вы должны представлять, как добавить на свои собственные Node.js-сайты простые формы и код для их обработки!

Смотрите также
--------------

* Пакет express-validator (документация на npm).
* Назад
* Обзор: Express Nodejs
* Далее  (en-US)
Содержание этого модуля (блока статей)
--------------------------------------

* Введение в Express/Node
* Настройка окружения для разработки на Node.js (Express)
* Учебник Express: сайт Local Library
* Учебник Express, часть 2: Создание скелета сайта
* Учебник Express, часть 3: Использование базы данных (с помощью Mongoose)
* Учебник Express, часть 4: Маршруты и контроллеры
* Учебник Express, часть 5: Отображение данных библиотеки
* Учебник Express, часть 6: Работа с формами
* Учебник Express, часть 7: Ввод сайта в эксплуатацию (en-US)


Учебник Express часть 5: Отображение данных библиотеки - Изучение веб-разработки

Учебник Express часть 5: Отображение данных библиотеки
======================================================

* Назад
* Обзор: Express Nodejs
* Далее

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

| Предварительные знания: | Завершите изучение предыдущих тем учебника (включая Учебник Express часть 4: Маршруты и контроллеры). |
| --- | --- |
| Цель: | Понять, как использовать асинхронный модуль и язык шаблона Pug, и как получить данные из URL в наших функциях контроллера. |

Обзор
-----

В предыдущих статьях учебника мы определили Mongoose модели, которые можно использовать для взаимодействия с базой данных и создания некоторых исходных записей библиотеки. Затем мы создали все маршруты, необходимые для веб-сайта LocalLibrary, но с "фиктивными" функциями контроллеров (это скелетные функции, которые просто возвращают сообщение "не реализовано " при доступе к странице).

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

Мы начнём с обзорных / основных тем, объясняющих, как управлять асинхронными операциями в функциях контроллера и как писать шаблоны с помощью Pug. Затем мы предоставим реализации для каждой из наших основных страниц" только для чтения " с кратким объяснением любых специальных или новых функций, которые они используют.

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

Отображение данных библиотеки — подразделы
------------------------------------------

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

1. Асинхронное управление потоками с помощью async (en-US)
2. Пример шаблона
3. Базовые шаблоны LocalLibrary
4. Домашняя страница
5. Страница списка книг
6. Страница списка экземпляров книг
7. Форматирование даты с момента использования
8. Страница списка авторов и страница списка жанров
9. Страница сведений о жанре
10. Страница сведений о книге (en-US)
11. Страница информации об авторе (en-US)
12. Страница сведений об экземпляре книги и вызове (en-US)
Итог
----

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

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

Смотрите также
--------------

* Асинхронный модуль (Асинхронные документация)
* Использование механизмов шаблонов с Express (Express документация)
* Pug (Pug документация)
* Moment (Moment документация)
* Назад
* Обзор: Express Nodejs
* Далее
In this module
--------------

* Express/Node introduction
* Setting up a Node (Express) development environment
* Express Tutorial: The Local Library website
* Express Tutorial Part 2: Creating a skeleton website
* Express Tutorial Part 3: Using a Database (with Mongoose)
* Express Tutorial Part 4: Routes and controllers
* Express Tutorial Part 5: Displaying library data
* Express Tutorial Part 6: Working with forms
* Express Tutorial Part 7: Deploying to production (en-US)


Setting up a Node development environment - Изучение веб-разработки

Setting up a Node development environment
=========================================

* Назад
* Обзор: Express Nodejs
* Далее

Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Express для Windows, Linux (Ubuntu) и Mac OS X - какую бы операционную систему вы не использовали, эта статья должна дать вам все, что необходимо для возможности начать разрабатывать приложения Express.

| Требования: | Знание как открыть терминал / командную строку, как устанавливать программные пакеты в операционной системе вашего компьютера. |
| --- | --- |
| Задача: | Создать среду разработки для Express на вашем компьютере. |

Обзор среды разработки Express
------------------------------

*Node* и *Express* упрощают настройку вашего компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, какие инструменты нужны, приводятся некоторые из самых простых способов установки Node (и Express) на Ubuntu, macOS, and Windows, и показывается как вы можете протестировать свою установку.

### Что такое среда разработки Express?

Среда разработки Express включает в себя установку Nodejs, менеджера пакетов NPM и (необязательно) Express Application Generator на локальном компьютере.

Узел и менеджер пакетов NPM устанавливаются вместе из подготовленных двоичных пакетов, установщиков, менеджеров пакетов операционной системы или из исходного кода (как показано в следующих разделах). Затем Express устанавливается NPM как зависимость от ваших отдельных веб-приложений Express (наряду с другими библиотеками, такими как механизмы шаблонов, драйверы баз данных, промежуточное программное обеспечение для аутентификации, промежуточное программное обеспечение для обслуживания статических файлов и т. Д.)

NPM также можно использовать для (глобальной) установки Express Application Generator, удобного инструмента для создания каркасных веб-приложений Express, которые следуют шаблону MVC. Генератор приложений является необязательным, поскольку вам не нужно использовать этот инструмент для создания приложений, использующих Express, или для приложений для создан Express, имеющих одинаковую архитектурную разметку или зависимости. Мы будем использовать его, потому что это значительно облегчает начало работы и продвигает модульную структуру приложения.

**Примечание:** Примечание: в отличие от некоторых других веб-сред, среда разработки не включает отдельный веб-сервер разработки. В Node / Express веб-приложение создаёт и запускает собственный веб-сервер!

Существуют и другие периферийные инструменты, которые являются частью типичной среды разработки, в том числе текстовые редакторы или IDE для редактирования кода и инструменты управления исходным кодом, такие как Git, для безопасного управления различными версиями вашего кода. Мы предполагаем, что вы уже установили подобные инструменты (в частности, текстовый редактор).

### Какие операционные системы поддерживаются?

Узел может быть запущен в Windows, macOS, во многих «разновидностях» Linux, Docker и т. Д. (Полный список на странице загрузок nodejs). Практически любой персональный компьютер должен иметь необходимую производительность для запуска Node во время разработки. Express работает в среде Node и, следовательно, может работать на любой платформе, на которой работает Node.

В этой статье мы предоставляем инструкции по установке для Windows, macOS и Ubuntu Linux.

### Какую версию Node / Express следует использовать?

Существует множество выпусков Node - более новые выпуски содержат исправления ошибок, поддержку более свежих версий стандартов ECMAScript (JavaScript) и улучшения API-интерфейсов Node.

Как правило, вы должны использовать самый последний выпуск LTS (с долгосрочной поддержкой), поскольку он будет более стабильным, чем «текущий» выпуск, при этом все ещё имея относительно недавние функции (и все ещё активно поддерживается). Вы должны использовать Текущий выпуск, если вам нужна функция, которой нет в версии LTS.

Для Express вы всегда должны использовать последнюю версию.

### Как насчёт баз данных и других зависимостей?

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

Установка Node
--------------

Чтобы использовать Express, сначала необходимо установить Nodejs и Node Package Manager (NPM) в вашей операционной системе. В следующих разделах описывается самый простой способ установки версии Nodejs с долгосрочной поддержкой (LTS) в Ubuntu Linux 16.04, macOS и Windows 10.

**Примечание:** Совет: В следующих разделах показан самый простой способ установки Node и NPM на наши целевые платформы ОС. Если вы используете другую ОС или просто хотите увидеть некоторые другие подходы для текущих платформ, см. Установка Node.js через менеджер пакетов (nodejs.org).

### Windows и macOS

Установка Node и NPM в Windows и macOS проста, потому что вы можете просто использовать предоставленный инсталлятор:

1. Загрузите необходимый установщик:
    1. Перейдите по ссылке https://nodejs.org/en/
    2. Нажмите кнопку, чтобы загрузить сборку LTS, которая «Рекомендуется для большинства пользователей».
2. Установите Node, дважды щёлкнув по загруженному файлу и следуя инструкциям по установке.
### Ubuntu 16.04

Самый простой способ установить последнюю версию LTS Node 6.x - это использовать package manager чтобы получить его из репозитория бинарных дистрибутивов Ubuntu. Это можно сделать очень просто, выполнив следующие две команды на вашем терминале:

```bash
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
```

**Предупреждение:** **Внимание:** Не устанавливайте напрямую из обычных репозиториев Ubuntu, поскольку они содержат очень старые версии узла.

### Проверка вашей установки Nodejs и NPM

Самый простой способ проверить, установлен ли этот узел, - это запустить команду «версия» в своём терминале / командной строке и проверить, что возвращается строка версии:

```bash
>node -v
v8.11.3
```

Менеджер пакетов Nodejs NPM также должен быть установлен и может быть протестирован таким же образом:

```bash
>npm -v
5.8.0
```

В качестве немного более захватывающего теста давайте создадим очень простой сервер «чистого узла», который просто печатает «Hello World» в браузере, когда вы посещаете правильный URL в вашем браузере:

1. Скопируйте следующий текст в файл с именем hellonode.js. Здесь используются чистые функции Node (ничего из Express) и некоторый синтаксис ES6:

```js
//Загрузка модуля HTTP
const http = require("http");
const hostname = "127.0.0.1";
const port = 3000;

//Создание HTTP-сервера
const server = http.createServer((req, res) => {
  //Установка HTTP-заголовков ответа с HTTP-статусом и Content type
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/plain");
  res.end("Hello World\n");
});

//Слушание запросов на порту 3000, и в качестве каллбака функция, которая пишет в лог
server.listen(port, hostname, () => {
  console.log("Server running at http://${hostname}:${port}/");
});
```

Код импортирует модуль «http» и использует его для создания сервера (createServer ()), который обрабатывает HTTP-запросы на порту 3000. Затем сценарий выводит на консоль сообщение о том, какой URL-адрес браузера можно использовать для тестирования сервера. Функция createServer () принимает в качестве аргумента колбэк-функцию, которая будет вызываться при получении HTTP-запроса - она просто возвращает ответ с кодом состояния HTTP 200 («ОК») и простым текстом «Hello World».

**Примечание:** Замечание: не беспокойтесь, если вы ещё не совсем понимаете, что делает этот код! Мы объясним наш код более подробно, как только мы начнём использовать Express!
2. Запустите сервер, перейдя в тот же каталог, что и ваш файл hellonode.js в командной строке, и вызвав узел вместе с именем скрипта, например так:

```bash
>node hellonode.js
Server running at http://127.0.0.1:3000/
```

3. Перейдите к URL-адресу `http://127.0.0.1:3000`. Если все работает, браузер должен просто отобразить строку «Hello World».
Использование NPM
-----------------

Помимо самого Node, NPM является наиболее важным инструментом для работы с приложениями Node. NPM используется для получения любых пакетов (библиотек JavaScript), которые необходимы приложению для разработки, тестирования и / или производства, а также может использоваться для запуска тестов и инструментов, используемых в процессе разработки.

**Примечание:** С точки зрения Node, Express - это просто ещё один пакет, который вам нужно установить с помощью NPM, а затем установить его в своём собственном коде.

Вы можете вручную использовать NPM для получения каждого необходимого пакета отдельно. Обычно мы вместо этого управляем зависимостями, используя простой текстовый файл с именем package.json. В этом файле перечислены все зависимости для конкретного «пакета» JavaScript, включая имя пакета, версию, описание, исходный файл для выполнения, производственные зависимости, зависимости разработки, версии Node, с которыми он может работать, и т. Д. Файл package.json должен содержать все, что нужно NPM для загрузки и запуска вашего приложения (если вы пишете библиотеку многократного использования, вы можете использовать это определение для загрузки пакета в репозиторий npm и сделать его доступным для других пользователей).

### Добавление зависимостей

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

**Примечание:** Здесь мы показываем инструкции для получения и установки пакета Express. Позже мы покажем, как этот пакет и другие уже указаны для нас с помощью Express Application Generator. Этот раздел предоставлен, потому что полезно понять, как работает NPM и что создаётся генератором приложений.

1. Сначала создайте каталог для вашего нового приложения и перейдите в него:

```bash
mkdir myapp
cd myapp
```

2. Используйте команду npm init для создания файла package.json для вашего приложения. Эта команда запрашивает у вас несколько вещей, включая имя и версию вашего приложения, а также имя исходного файла точки входа (по умолчанию это index.js). Сейчас просто примите значения по умолчанию:

```bash
npm init
```

Если вы отобразите файл package.json (cat package.json), вы увидите принятые по умолчанию значения, заканчивающиеся лицензией.

```json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

3. Теперь установите Express в каталог myapp и сохраните его в списке зависимостей вашего файла package.json
4.
```bash
npm install express --save
```

Раздел зависимостей вашего package.json теперь появится в конце файла package.json и будет содержать Express.

```json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3"
  }
}
```

5. Для использования библиотеки вы вызываете функцию require (), как показано ниже в вашем файле index.js.

```js
const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(8000, () => {
  console.log("Example app listening on port 8000!");
});
```

Этот код показывает минимальное веб-приложение Express «HelloWorld». Это импортирует модуль «экспресс» и использует его для создания сервера (приложения), который обрабатывает HTTP-запросы на порту 8000 и выводит на консоль сообщение, объясняющее, какой URL-адрес браузера можно использовать для тестирования сервера. Функция app.get () отвечает только на запросы HTTP GET с указанным URL-путём ('/'), в этом случае вызывая функцию для отправки нашего Hello World! сообщение.
 Создайте файл с именем index.js в корне каталога приложения «myapp» и передайте ему содержимое, показанное выше.
6. Вы можете запустить сервер, вызвав узел с помощью скрипта в командной строке:

```bash
>node index.js
Example app listening on port 8000
```

7. Перейдите к URL (`http://127.0.0.1:8000/`) . Если все работает, браузер должен просто отобразить строку «Hello World!».
### Зависимости разработки

Если зависимость используется только во время разработки, вы должны вместо этого сохранить её как «зависимость разработки» (чтобы пользователям вашего пакета не приходилось устанавливать её в производстве). Например, чтобы использовать популярный инструмент JavaScript Linting eslint, вы должны вызвать NPM, как показано ниже:

```bash
npm install eslint --save-dev
```

Следующая запись будет добавлена в package.json вашего приложения:

```json
  "devDependencies": {
    "eslint": "^4.12.1"
  }
```

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

### Запуск задач

В дополнение к определению и извлечению зависимостей вы также можете определить именованные скрипты в ваших файлах package.json и вызвать NPM, чтобы выполнить их с помощью команды run-script. Этот подход обычно используется для автоматизации выполнения тестов и частей набора инструментов разработки или сборки (например, запуска инструментов для минимизации JavaScript, сжатия изображений, LINT / анализа вашего кода и т. Д.).

**Примечание:** Для запуска тестов и других внешних инструментов могут также использоваться такие исполнители, как Gulp и Grunt.

Например, чтобы определить скрипт для запуска зависимости разработки eslint, которую мы указали в предыдущем разделе, мы могли бы добавить следующий блок скрипта в наш файл package.json (при условии, что наш источник приложения находится в папке / src / js):

```json
"scripts": {
  ...
  "lint": "eslint src/js"
  ...
}
```

Чтобы пояснить немного подробнее, eslint src / js - это команда, которую мы могли бы ввести в нашем терминале / командной строке, чтобы запустить eslint для файлов JavaScript, содержащихся в каталоге src / js внутри каталога нашего приложения. Включение вышеупомянутого в файл package.json нашего приложения обеспечивает ярлык для этой команды - lint.

Затем мы сможем запустить eslint с помощью NPM, вызвав:

```bash
npm run-script lint
# OR (using the alias)
npm run lint
```

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

Установка Express Application Generator
---------------------------------------

Инструмент Express Application Generator создаёт «скелет» приложения Express. Установите генератор, используя NPM, как показано (флаг -g устанавливает инструмент глобально, чтобы вы могли вызывать его из любого места):
```

npm install express-generator -g
```

Чтобы создать приложение Express с именем «helloworld» с настройками по умолчанию, перейдите туда, где вы хотите его создать, и запустите приложение, как показано ниже:

```bash
express helloworld
```

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

```bash
express --help
```

NPM создаст новое приложение Express в подпапке вашего текущего местоположения, отображая процесс сборки на консоли. По завершении инструмент отобразит команды, которые необходимо ввести, чтобы установить зависимости Node и запустить приложение.

**Примечание:** Новое приложение будет иметь файл package.json в своём корневом каталоге. Вы можете открыть это, чтобы увидеть, какие зависимости установлены, включая Express и библиотеку шаблонов Jade:

```js
{
  "name": "helloworld",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.18.2",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.9",
    "express": "~4.15.5",
    "jade": "~1.11.0",
    "morgan": "~1.9.0",
    "serve-favicon": "~2.4.5"
  }
}
```

Установите все зависимости для приложения helloworld, используя NPM, как показано ниже:

```bash
cd helloworld
npm install
```

Затем запустите приложение (команды немного отличаются для Windows и Linux / macOS), как показано ниже:

```bash
# Run the helloworld on Windows with Command Prompt
SET DEBUG=helloworld:* & npm start

# Run the helloworld on Windows with PowerShell
SET DEBUG=helloworld:* | npm start

# Run helloworld on Linux/macOS
DEBUG=helloworld:* npm start
```

Команда DEBUG создаёт полезное ведение журнала, что приводит к выводу, подобному показанному ниже.

```bash
>SET DEBUG=helloworld:* & npm start

> helloworld@0.0.0 start D:\Github\expresstests\helloworld
> node ./bin/www

  helloworld:server Listening on port 3000 +0ms
```

Откройте браузер и перейдите по адресу `http://127.0.0.1:3000/`, чтобы увидеть страницу приветствия Express по умолчанию.

![Express - Generated App Default Screen](/ru/docs/Learn/Server-side/Express_Nodejs/development_environment/express_default_screen.png)

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

Резюме
------

Теперь на вашем компьютере установлена и запущена среда разработки Node, которую можно использовать для создания веб-приложений Express. Вы также увидели, как NPM можно использовать для импорта Express в приложение, а также как вы можете создавать приложения с помощью инструмента Express Application Generator и затем запускать их.

В следующей статье мы начнём работу с учебным пособием по созданию полноценного веб-приложения с использованием этой среды и связанных инструментов.

Смотрите также
--------------

* Downloads page (nodejs.org)
* Installing Node.js via package manager (nodejs.org)
* Installing Express (expressjs.com)
* Express Application Generator (expressjs.com)
* Назад
* Обзор: Express Nodejs
* Далее


Update Book form - Изучение веб-разработки

Update Book form
================

Наконец, в разделе показано, как определить страницу для обновления объектов `Book`. Обработка форм при обновлении книги аналогична обработке форм при создании книги, за исключением того, что необходимо заполнить форму в маршруте `GET` значениями из базы данных.

Controller—get route
--------------------

Откройте **/controllers/bookController.js**. Найдите экспортируемый метод контроллера `book_update_get()` и замените его на следующий код.

```js
// Display book update form on GET.
exports.book\_update\_get = function (req, res, next) {
  // Get book, authors and genres for form.
  async.parallel(
    {
      book: function (callback) {
        Book.findById(req.params.id)
          .populate("author")
          .populate("genre")
          .exec(callback);
      },
      authors: function (callback) {
        Author.find(callback);
      },
      genres: function (callback) {
        Genre.find(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      }
      if (results.book == null) {
        // No results.
        var err = new Error("Book not found");
        err.status = 404;
        return next(err);
      }
      // Success.
      // Mark our selected genres as checked.
      for (
        var all_g_iter = 0;
        all_g_iter < results.genres.length;
        all_g_iter++
      ) {
        for (
          var book_g_iter = 0;
          book_g_iter < results.book.genre.length;
          book_g_iter++
        ) {
          if (
            results.genres[all_g_iter]._id.toString() ==
            results.book.genre[book_g_iter]._id.toString()
          ) {
            results.genres[all_g_iter].checked = "true";
          }
        }
      }
      res.render("book\_form", {
        title: "Update Book",
        authors: results.authors,
        genres: results.genres,
        book: results.book,
      });
    },
  );
};
```

Контроллер получит id `Book` книги для обновления из параметра URL (`req.params.id`). Он использует метод `async.parallel()` чтобы получить указанную запись `Book` (заполнение полей жанра и автора) и список всех объектов `Author` и `Genre`. Когда все операции завершены, он помечает выбранные жанры как отмеченные, а затем отображает их в **book\_form.pug**, передаёт переменные `title`, book, всех `authors`, и все `genres`.

Controller—post route
---------------------

Найдите экспортируемый метод контроллера `book_update_post()` и замените его следующим кодом.

```js
// Handle book update on POST.
exports.book_update_post = [
  // Convert the genre to an array
  (req, res, next) => {
    if (!(req.body.genre instanceof Array)) {
      if (typeof req.body.genre === "undefined") req.body.genre = [];
      else req.body.genre = new Array(req.body.genre);
    }
    next();
  },

  // Validate fields.
  body("title", "Title must not be empty.").isLength({ min: 1 }).trim(),
  body("author", "Author must not be empty.").isLength({ min: 1 }).trim(),
  body("summary", "Summary must not be empty.").isLength({ min: 1 }).trim(),
  body("isbn", "ISBN must not be empty").isLength({ min: 1 }).trim(),

  // Sanitize fields.
  sanitizeBody("title").trim().escape(),
  sanitizeBody("author").trim().escape(),
  sanitizeBody("summary").trim().escape(),
  sanitizeBody("isbn").trim().escape(),
  sanitizeBody("genre.\*").trim().escape(),

  // Process request after validation and sanitization.
  (req, res, next) => {
    // Extract the validation errors from a request.
    const errors = validationResult(req);

    // Create a Book object with escaped/trimmed data and old id.
    var book = new Book({
      title: req.body.title,
      author: req.body.author,
      summary: req.body.summary,
      isbn: req.body.isbn,
      genre: typeof req.body.genre === "undefined" ? [] : req.body.genre,
      \_id: req.params.id, //This is required, or a new ID will be assigned!
    });

    if (!errors.isEmpty()) {
      // There are errors. Render form again with sanitized values/error messages.

      // Get all authors and genres for form.
      async.parallel(
        {
          authors: function (callback) {
            Author.find(callback);
          },
          genres: function (callback) {
            Genre.find(callback);
          },
        },
        function (err, results) {
          if (err) {
            return next(err);
          }

          // Mark our selected genres as checked.
          for (let i = 0; i < results.genres.length; i++) {
            if (book.genre.indexOf(results.genres[i]._id) > -1) {
              results.genres[i].checked = "true";
            }
          }
          res.render("book\_form", {
            title: "Update Book",
            authors: results.authors,
            genres: results.genres,
            book: book,
            errors: errors.array(),
          });
        },
      );
      return;
    } else {
      // Data from form is valid. Update the record.
      Book.findByIdAndUpdate(req.params.id, book, {}, function (err, thebook) {
        if (err) {
          return next(err);
        }
        // Successful - redirect to book detail page.
        res.redirect(thebook.url);
      });
    }
  },
];
```

Это очень похоже на маршрут записи, используемый при создании Book. Сперва мы проверяем и очищаем данные книги и используем их для создание нового объекта `Book` (устанавливая его значение `_id` в идентификатор объекта для обновления). Если есть ошибки, когда мы проверяем данные, то мы повторно представляем форму, дополнительно отображая данные, введённые пользователем, ошибки, а также списки жанров и авторов. Если ошибок нет, то мы вызываем `Book.findByIdAndUpdate()` для обновления документа `Book`, а затем перенаправить на страницу сведений.

View
----

Откройте **/views/book\_form.pug** и обновите раздел, в котором элемент управления "форма автора" имеет условный код, показанный ниже.

```pug
    div.form-group
      label(for='author') Author:
      select#author.form-control(type='select' placeholder='Select author' name='author' required='true' )
        for author in authors
          if book
            //- Handle GET form, where book.author is an object, and POST form, where it is a string.
            option(
 value=author.\_id
 selected=(
 author.\_id.toString()==book.author.\_id
                || author._id.toString()==book.author
              ) ? 'selected' : false
            ) #{author.name}
          else
            option(value=author.\_id) #{author.name}
```

**Примечание:** Это изменение кода необходимо для того, чтобы форму book\_form можно было использовать как для создания, так и для обновления объектов book (без этого при создании формы на маршруте `GET` возникает ошибка).

Добавить кнопку обновления
--------------------------

Откройте **book\_detail.pug** и убедитесь, что есть ссылки для удаления и обновления книг в нижней части страницы, как показано ниже.

```pug
  hr
  p
    a(href=book.url+'/delete') Delete Book
  p
    a(href=book.url+'/update') Update Book
```

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

Как это выглядит?
-----------------

Запустите приложение, откройте ваш браузер на http://localhost:3000/, выберите ссылку *All books*, затем выберите конкретную книгу. Наконец, выберите ссылку *Update Book*.

Форма должна выглядеть так же, как страница *Create book*, только с заголовком 'Update book' и предварительно заполнены значениями записей.

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form/locallibary_express_book_update_noerrors.png)

**Примечание:** Другие страницы для обновления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задание.

Next steps
----------

* Return to Express Tutorial Part 6: Working with forms.


Delete Author form - Изучение веб-разработки

Delete Author form
==================

В этой статье показано, как определить страницу для удаления объектов `Author`.

Как описано в разделе form design, наша стратегия будет заключаться в том, чтобы разрешить удаление только объектов, на которые не ссылаются другие объекты(в этом случае это означает, что мы не позволим `Author` быть удалённым, если на него ссылается `Book`). С точки зрения реализации это означает, что форма должна подтвердить, что нет никаких связанных книг, прежде чем автор будет удалён. Если есть связанные книги, то они должны отображаться и быть удалены до того, как будет удалён объект `Author`.

Controller—get route
--------------------

Откройте **/controllers/authorController.js**. Найдите экспортируемый метод контроллера `author_delete_get()` и замените его на следующий код.

```js
// Отображать форму для удаления автора GET
exports.author\_delete\_get = function (req, res, next) {
  async.parallel(
    {
      author: function (callback) {
        Author.findById(req.params.id).exec(callback);
      },
      authors\_books: function (callback) {
        Book.find({ author: req.params.id }).exec(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      }
      if (results.author == null) {
        // No results.
        res.redirect("/catalog/authors");
      }
      // Удачно, значит рендерим.
      res.render("author\_delete", {
        title: "Delete Author",
        author: results.author,
        author\_books: results.authors_books,
      });
    },
  );
};
```

Контроллер получает id экземпляра `Author` для удаления из параметра URL (`req.params.id`). Он использует метод `async.parallel()` , чтобы получить запись автора и параллельно вс связанные книги. Когда оба параметра авершины, он рендерит страницу **`author_delete`**.pug, передаёт значения для `title`, `author`, и `author_books`.

**Примечание:** Если `findById()` не возвращает результатов, то автор отсутствует в базе данных. В этом случае удалять нечего, поэтому сразу выводим список всех авторов.

```js
}, function(err, results) {
    if (err) { return next(err); }
    if (results.author==null) { // No results.
        res.redirect('/catalog/authors')
    }
```

Controller—post route
---------------------

Найдите экспортируемый метод контроллера `author_delete_post()` и замените его на следующий код.

```js
// Обработчик удаления автора POST.
exports.author\_delete\_post = function (req, res, next) {
  async.parallel(
    {
      author: function (callback) {
        Author.findById(req.body.authorid).exec(callback);
      },
      authors\_books: function (callback) {
        Book.find({ author: req.body.authorid }).exec(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      }
      // Success
      if (results.authors_books.length > 0) {
        // Автор книги. Визуализация выполняется так же, как и для GET route.
        res.render("author\_delete", {
          title: "Delete Author",
          author: results.author,
          author\_books: results.authors_books,
        });
        return;
      } else {
        //У автора нет никаких книг. Удалить объект и перенаправить в список авторов.
        Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) {
          if (err) {
            return next(err);
          }
          // Успех-перейти к списку авторов
          res.redirect("/catalog/authors");
        });
      }
    },
  );
};
```

Сначала мы проверяем, что был предоставлен id (он отправляется через параметры тела формы, а не через версию в URL). Затем мы получаем автора и связанные с ним книги так же, как и для маршрута `GET`. Если книг нет, то удаляем объект автора и перенаправляем в список всех авторов. Если есть ещё книги, то мы просто перерисовываем форму, передавая автора и список книг, которые нужно удалить.

**Примечание:** Мы можем проверить, возвращает ли вызов `findbyid ()` какой-либо результат, и если нет, немедленно отобразить список всех авторов.Для краткости мы оставили код как есть выше (он всё равно вернёт список авторов, если id не будет найден, но это произойдёт после `findByIdAndRemove()`).

View
----

Создайте **/views/author\_delete.pug** и скопируйте текст ниже.

```pug
extends layout

block content
  h1 #{title}: #{author.name}
  p= author.lifespan

  if author\_books.length

    p #[strong Delete the following books before attempting to delete this author.]

    div(style='margin-left:20px;margin-top:20px')

      h4 Books

      dl
      each book in author\_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

  else
    p Do you really want to delete this Author?

    form(method='POST' action='')
      div.form-group
        input#authorid.form-control(type='hidden',name='authorid', required='true', value=author.\_id )

      button.btn.btn-primary(type='submit') Delete
```

Представление расширяет шаблон макета, переопределяя блок с именем `content`. Вверху отображаются сведения об авторе. Затем он включает условный оператор, основанный на количестве **`author_books`** (пункты `if` и `else` ).

* Если есть книги, связанные с автором, то на странице перечислены книги и говорится, что они должны быть удалены, прежде чем этот `Author` может быть удалён.
* Если книг нет, на странице отображается запрос на подтверждение. Если нажать кнопку **Delete**, то id автора будет отправлен на сервер в `POST`-запросе, и запись этого автора будет удалена.
Добавление элемента управления delete
-------------------------------------

Затем мы добавим элемент управления `Delete` в представление сведений об авторе (страница сведений-хорошее место для удаления записи).

**Примечание:** В полном объёме контроль будет доступен только авторизованным пользователям. Однако на данный момент у нас нет системы авторизации!

Откройте **author\_detail.pug** и добавьте следующие строки внизу.

```pug
hr
p
  a(href=author.url+'/delete') Delete author
```

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

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form/locallibary_express_author_detail_delete.png)

Как это выглядит?
-----------------

Запустите приложение и откройте в вашем браузере http://localhost:3000/. Затем раздел *All authors* , а затем укажите конкретного пользователя. Наконец, выберите ссылку *Delete author*.

Если у автора нет книг, вам будет представлена такая страница. После нажатия клавиши delete сервер удалит автора и перенаправит в список авторов

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form/locallibary_express_author_delete_nobooks.png)

Если у автора есть книги, то вам будет представлен следующий вид. Затем вы можете удалить книги из их подробных страниц (как только этот код будет реализован!).

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form/locallibary_express_author_delete_withbooks.png)

**Примечание:** Другие страницы для удаления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задачи.

Next steps
----------

* Return to Express Tutorial Part 6: Working with forms.
* Proceed to the final subarticle of part 6: Update Book form.


Форма для создания BookInstance - Изучение веб-разработки

Форма для создания BookInstance
===============================

В этой статье показано, как определить страницу / форму для создания объектов `BookInstance`. Это очень похоже на форму, которую мы использовали для создания объектов `Book`.

Импорт методов проверки и очистки
---------------------------------

Откройте **/controllers/bookinstanceController.js** и добавьте следующие строки вверху файла:

```js
const { body, validationResult } = require("express-validator/check");
const { sanitizeBody } = require("express-validator/filter");
```

Controller—get route
--------------------

At the top of the file, require the *Book* module (needed because each `BookInstance` is associated with a particular `Book`).

```js
var Book = require("../models/book");
```

Find the exported `bookinstance_create_get()` controller method and replace it with the following code.

```js
// Display BookInstance create form on GET.
exports.bookinstance\_create\_get = function (req, res, next) {
  Book.find({}, "title").exec(function (err, books) {
    if (err) {
      return next(err);
    }
    // Successful, so render.
    res.render("bookinstance\_form", {
      title: "Create BookInstance",
      book\_list: books,
    });
  });
};
```

The controller gets a list of all books (`book_list`) and passes it to the view **`bookinstance_form.pug`** (along with the `title`)

Controller—post route
---------------------

Find the exported `bookinstance_create_post()` controller method and replace it with the following code.

```js
// Handle BookInstance create on POST.
exports.bookinstance_create_post = [
  // Validate fields.
  body("book", "Book must be specified").isLength({ min: 1 }).trim(),
  body("imprint", "Imprint must be specified").isLength({ min: 1 }).trim(),
  body("due\_back", "Invalid date").optional({ checkFalsy: true }).isISO8601(),

  // Sanitize fields.
  sanitizeBody("book").escape(),
  sanitizeBody("imprint").escape(),
  sanitizeBody("status").trim().escape(),
  sanitizeBody("due\_back").toDate(),

  // Process request after validation and sanitization.
  (req, res, next) => {
    // Extract the validation errors from a request.
    const errors = validationResult(req);

    // Create a BookInstance object with escaped and trimmed data.
    var bookinstance = new BookInstance({
      book: req.body.book,
      imprint: req.body.imprint,
      status: req.body.status,
      due\_back: req.body.due_back,
    });

    if (!errors.isEmpty()) {
      // There are errors. Render form again with sanitized values and error messages.
      Book.find({}, "title").exec(function (err, books) {
        if (err) {
          return next(err);
        }
        // Successful, so render.
        res.render("bookinstance\_form", {
          title: "Create BookInstance",
          book\_list: books,
          selected\_book: bookinstance.book._id,
          errors: errors.array(),
          bookinstance: bookinstance,
        });
      });
      return;
    } else {
      // Data from form is valid.
      bookinstance.save(function (err) {
        if (err) {
          return next(err);
        }
        // Successful - redirect to new record.
        res.redirect(bookinstance.url);
      });
    }
  },
];
```

The structure and behaviour of this code is the same as for creating our other objects. First we validate and sanitize the data. If the data is invalid, we then re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid, we save the new `BookInstance` record and redirect the user to the detail page.

View
----

Create **/views/bookinstance\_form.pug** and copy in the text below.

```pug
extends layout

block content
  h1=title

  form(method='POST' action='')
    div.form-group
      label(for='book') Book:
      select#book.form-control(type='select' placeholder='Select book' name='book' required='true')
        - book\_list.sort(function(a, b) {let textA = a.title.toUpperCase(); let textB = b.title.toUpperCase(); return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;});
        for book in book\_list
          if bookinstance
            option(value=book.\_id selected=(bookinstance.book.toString()==book.\_id.toString() ? 'selected' : false)) #{book.title}
          else
            option(value=book.\_id) #{book.title}

    div.form-group
      label(for='imprint') Imprint:
      input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint))
    div.form-group
      label(for='due\_back') Date when book available:
      input#due\_back.form-control(type='date' name='due\_back' value=(undefined===bookinstance ? '' : bookinstance.due\_back))

    div.form-group
      label(for='status') Status:
      select#status.form-control(type='select' placeholder='Select status' name='status' required='true')
        option(value='Maintenance') Maintenance
        option(value='Available') Available
        option(value='Loaned') Loaned
        option(value='Reserved') Reserved

    button.btn.btn-primary(type='submit') Submit

  if errors
    ul
      for error in errors
        li!= error.msg
```

The view structure and behaviour is almost the same as for the **book\_form.pug** template, so we won't go over it again.

**Примечание:** The above template hard-codes the *Status* values (Maintenance, Available, etc.) and does not "remember" the user's entered values. Should you so wish, consider reimplementing the list, passing in option data from the controller and setting the selected value when the form is re-displayed.

Как это выглядит?
-----------------

Запустите приложение и откройте в браузере http://localhost:3000/. Затем выберите ссылку *Create new book instance (copy)*. Если все настроено правильно, ваш сайт должен выглядеть примерно так, как показано на скриншоте. После того, как вы отправите валидный `BookInstance`, он должен быть сохранён, и вы попадёте на страницу сведений.

![](/ru/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form/locallibary_express_bookinstance_create_empty.png)

Next steps
----------

* Return to Express Tutorial Part 6: Working with forms.
* Proceed to the next subarticle of part 6: Delete Author form.


Home page - Изучение веб-разработки

Home page
=========

Первой создаваемой страницей будет главная страница сайта, доступная из корня сайта (`/`) или из каталога (`catalog/`). На странице будет виден статический текст с описанием и динамически вычисляемые "количества" записей разных типов, имеющиеся в базе данных.

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

**Примечание:** Мы будем использовать Mongoose для получения информации из базы данных.
 Поэтому перед продолжением чтения может быть полезным ознакомится с Учебником по Mongoose.

Маршрут
-------

Мы создали маршруты главной страницы в предыдущем разделе. Напомним, все функции маршрутов определены в файле **/routes/catalog.js**:

```js
// Получение главной страницы каталога.
router.get("/", book_controller.index); // Фактически происходит сопоставление с /catalog/, потому что мы импортируем маршрут с префиксом /catalog
```

Главная функция контроллера книг передаваемая в качестве параметра (`book_controller.index`) имеет реализацию-«заглушку» в **/controllers/bookController.js**:

```js
exports.index = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
});
```

Именно эту функцию контроллера мы расширим, чтобы получать информацию из моделей и затем отображать её, используя шаблон.

Контроллер
----------

Главная функция контроллера должна получать информацию о количестве книг (`Book`), экземпляров книг (`BookInstance`), их доступности, сколько авторов (`Author`) и жанров (`Genre`) имеется в базе. Потом эти данные должны попадать в шаблон для возможности отображения в виде HTML-страницы, которая будет отправлена в HTTP-ответе.

Откройте файл **/controllers/bookController.js**. Почти в самом начале вы должны увидеть экспортируемую функцию `index()` .

```js
const Book = require("../models/book");
const asyncHandler = require("express-async-handler");

exports.index = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
});
```

 Замените весь код, показанный выше, на следующий фрагмент кода. Первое, что он делает — импортирует (`require()`) все модели. Это требуется для подсчёта количество записей.
 Также происходит импорт библиотеки `express-async-handler`, которая предоставляет возможность обработки исключений, возникающих в обработчиках маршрутов.

```js
const Book = require("../models/book");
const Author = require("../models/author");
const Genre = require("../models/genre");
const BookInstance = require("../models/bookinstance");

const asyncHandler = require("express-async-handler");

exports.index = asyncHandler(async (req, res, next) => {
  // Получаем информацию о количестве книг, экземпляров книг, авторов и жанров (параллельно)
  const [
    numBooks,
    numBookInstances,
    numAvailableBookInstances,
    numAuthors,
    numGenres,
  ] = await Promise.all([
    Book.countDocuments({}).exec(),
    BookInstance.countDocuments({}).exec(),
    BookInstance.countDocuments({ status: "Available" }).exec(),
    Author.countDocuments({}).exec(),
    Genre.countDocuments({}).exec(),
  ]);

  res.render("index", {
    title: "Local Library Home",
    book\_count: numBooks,
    book\_instance\_count: numBookInstances,
    book\_instance\_available\_count: numAvailableBookInstances,
    author\_count: numAuthors,
    genre\_count: numGenres,
  });
});
```

 Мы используем метод `countDocuments()` для получения количества экземпляров каждой модели.
 Этот метод вызывается у модели (можно также передавать дополнительные условия) и возвращает объект `Query`.
 Запрос может быть выполнен с помощью метода `exec()`, который возвращает `Promise`, который будет содержать результат или будет отклонён в случае ошибки.

 Поскольку запросы количества записей независимы друг от друга мы используем `Promise.all()` для их параллельного запуска.
 Этот метод возвращает новое обещание, выполнение которого мы `ожидаем` (выполнение приостанавливается внутри *этой функции* на ключевом слове `await`).
 Когда все запросы будут выполнены, то возвращаемое методом `all()` обещание будет заполнено результатом и продолжится выполнение функции.

После этого мы вызываем `res.render()`, указывая шаблон с именем '**index**' и объектом, который содержит результаты выполнения запросов к базе данных. Этот объект представляет собой набор пар «ключ-значение» для того, чтобы в шаблоне можно было обращаться к значениям по этим ключам.

 Обратите внимание, что код очень простой, потому что мы рассчитываем, что запросы к базе данных будут выполнены успешно.
 Если какая-либо операция будет неуспешна, то возникающее исключение будет перехвачено `asyncHandler()` и передано в следующий слой в цепочке обработчиков.

Представление
-------------

Откройте файл **/views/index.pug** и замените его содержимое на следующий код.

```pug
extends layout

block content
  h1= title
  p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network.

  h1 Dynamic content

  p The library has the following record counts:

  ul
    li #[strong Books:] !{book\_count}
    li #[strong Copies:] !{book\_instance\_count}
    li #[strong Copies available:] !{book\_instance\_available\_count}
    li #[strong Authors:] !{author\_count}
    li #[strong Genres:] !{genre\_count}
```

Представление довольно понятное. Мы расширили базовый шаблон **layout.pug**, переписали `block` с именем '**content**'. Первый заголовок `h1` будет содержать экранированное содержимое переменной `title`, которая передаётся в функцию `render()`. Далее следует краткое описание нашего сайта.

 Под заголовком *Dynamic content* мы выводим данные о количестве записей в каждой модели.
 Обратите внимание, что значения в шаблоне — это ключи из объекта, который мы передали в функцию `render()`.

**Примечание:** Мы не экранируем значения счётчиков (то есть мы используем синтаксис `!{}`) потому что эти значения рассчитываются системой. Если же информация поступает от пользователей, то мы должны экранировать переменные.

Как это будет выглядеть?
------------------------

Теперь мы сделали всё, что необходимо для отображения главной страницы. Запустим приложение и откроем в браузере `http://localhost:3000/`. Если всё сделано правильно, то сайт будет выглядеть примерно так, как показано на скриншоте.

![Главная страница - Сайт LocalLibrary](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page/locallibary_express_home.png)

**Примечание:** Вы пока не сможете использовать ссылки в боковой панели, потому что это ещё не реализовано. Если вы попробуете, то получите ошибки вида «NOT IMPLEMENTED: Book list».

Следующие шаги
--------------

* Вернуться к Учебник Express часть 5: Отображение данных библиотеки.
* Продолжить обучение (дополнительная статья к части 5): Страница списка книг.


Список авторов. Тест - список жанров - Изучение веб-разработки

Список авторов. Тест - список жанров
====================================

Страница списка авторов должна показывать список всех авторов, хранимых в БД, причём каждое имя автора должно быть связано со страницей подробностей для этого автора. Дата рождения автора и дата смерти должны выводиться в одной строке после имени автора.

Контроллер
----------

Функция контроллера списка авторов должна получить список всех элементов в `Author` , и передать этот список в шаблон для отображения.

Откройте файл **/controllers/authorController.js**. Найдите экспортируемый метод `author_list()` в начале файла и замените его следующим ниже кодом:

```js
// Display list of all Authors.
exports.author\_list = function (req, res, next) {
  Author.find()
    .sort([["family\_name", "ascending"]])
    .exec(function (err, list\_authors) {
      if (err) {
        return next(err);
      }
      //Successful, so render
      res.render("author\_list", {
        title: "Author List",
        author\_list: list_authors,
      });
    });
};
```

Метод использует такие функции модели как `find()`, `sort()` и `exec()` для того, чтобы вернуть все объекты `Author` отсортированными по `family_name` в алфавитном порядке. В вызове `exec()` колбэк-функция имеет первый параметр- объект ошибок (или `null`) и второй параметр - список всех авторов, если ошибок не было. При ошибках вызывается следующая функция промежуточного слоя с полученным значением объекта ошибок, а если ошибок не было, отображается шаблон **author\_list**(.pug), передавая странице `title` и список авторов (`author_list`).

Представление
-------------

Создайте файл **/views/author\_list.pug** и поместите в него следующий текст:

```js
extends layout

block content
  h1= title

  ul
    each author in author_list
      li
        a(href=author.url) #{author.name}
        |  (#{author.date_of_birth} - #{author.date_of_death})

    else
      li There are no authors.
```

Представление создано по тому же образцу, что и другие шаблоны.

Как это выглядит?
-----------------

Запустите приложение и откройте браузер с адресом http://localhost:3000/. Выберите ссылку *All authors*. Если все было сделано правильно, страница должна выглядеть примерно так, как на следующем скриншоте.

![Author List Page - Express Local Library site](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page/locallibary_express_author_list.png)

**Примечание:** Представление дат продолжительности жизни автора выглядит безобразно! Это можно исправить, если использовать тот же подход , который применялся для списка `BookInstance` (добавить в модель `Author` виртуальное свойство продолжительности жизни). Но в этот раз, однако, некоторые даты могут отсутствовать, и ссылки на несуществующие свойства игнорируются, если не задан строгий режим. Метод `moment()` возвращает текущее время, и нежелательно, чтобы отсутствующие даты форматировались как "сегодня". Один из способов состоит в том, чтобы форматирующая функция возвращала пустую строку, если дата не существует. Например:

`return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';`

Тест - страница списка жанров!
------------------------------

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

![Genre List - Express Local Library site](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page/locallibary_express_genre_list.png)

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

1. Следует отредактировать `genre_list()` в файле **/controllers/genreController.js**.
2. Реализация почти такая же, как и для контроллера `author_list()` .
    * Sort the results by name, in ascending order.
3. Отображающий шаблон должен быть назван **genre\_list.pug**.
4. Шаблону для отображения должны быть переданы переменные `title` (строка 'Genre List') и `genre_list` (the list of список жанров, который вернёт колбэк-функция `Genre.find()`.
5. Представление должно соответствовать скриншоту, приведённому ранее (оно должно иметь структуру и формат, похожие на таковые в представлении списка авторов, за исключением, конечно, продолжительности жизни, так как для жанров даты не заданы).
Далее
-----

Вернуться к части 5 - Express Tutorial Part 5: Displaying library data.

Перейти к следующему подразделу в части 5: подробная информация о жанрах (Genre detail page).


Форматирование даты при помощи moment - Изучение веб-разработки

Форматирование даты при помощи moment
=====================================

По умолчанию отображение дат наших моделей некрасиво: *Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)*. В этом разделе мы покажем, как можно обновить страницу списка *BookInstance List* из предыдущего раздела, чтобы представить поле `due_date` в более удобном формате: December 6th, 2016.

Подход, который будет использован, состоит в создании виртуального свойства в модели `BookInstance`, которое будет возвращать отформатированную дату. Форматирование будет производиться с использованием moment, легковесной библиотеки JavaScript для разбора, проверки, изменения и форматирования дат.

**Примечание:** Можно применять *moment* для форматирования непосредственно в шаблонах Pug, а можно отформатировать строку в других местах. Использование виртуального свойства позволяет получить дату, отформатированную точно так же, как при помощи `due_date`.

Установка moment
----------------

Ведите следующую команду в корне проекта:

```bash
npm install moment
```

Создание виртуального свойства
------------------------------

1. Откройте файл **./models/bookinstance.js**.
2. В начало файла введите строку для импортирования *moment*.

```js
var moment = require("moment");
```

Добавьте виртуальное свойство `due_back_formatted` сразу после свойства url.

```js
BookInstanceSchema.virtual("due\_back\_formatted").get(function () {
  return moment(this.due_back).format("MMMM Do, YYYY");
});
```

**Примечание:** Метод format method может вывести дату почти по любому образцу. Синтаксис для представления различных составляющих даты можно найти в документации ( moment documentation).

Обновляем представление
-----------------------

Откройте файл **/views/bookinstance\_list.pug** и замените `due_back` на `due_back_formatted`.

```js
      if val.status!='Available'
        //span (Due: #{val.due\_back} )
        span  (Due: #{val.due_back_formatted} )
```

Вот и все. Если вы перейдёте к *All book-instances* в боковом меню, вы должны увидеть все даты в привлекательном формате!

Далее
-----

* Вернуться к Express Tutorial Part 5: Displaying library data.
* Перейти к следующему подразделу части 5: Author list page and Genre list page challenge (страница списка авторов и тест- страница списка жанров).


Список экземпляров книг - Изучение веб-разработки

Список экземпляров книг
=======================

Далее мы реализуем список всех имеющихся в библиотеке копий книги (`BookInstance`) . Эта страница должна включать название книги из `Book`, с которой связаны экземпляры `BookInstance` (linked to its detail page), а также дополнительную информацию, имеющуюся в модели `BookInstance`, включая статус, издание, и уникальный идентификатор каждой копии. Уникальное значение идентификатора копии должно быть связано со страницей детальной информации `BookInstance`.

Контроллер
----------

Функция контроллера списка `BookInstance` требуется для получения списка всех экземпляров некоторой книги, для получения информации, связанной с книгой, и для передачи полученного списка в шаблон для отображения.

Откройте файл **/controllers/bookinstanceController.js**. Найдите экспортируемый метод `bookinstance_list()` контроллера и замените его следующим кодом (изменённый код выделен жирным).

```js
// Display list of all BookInstances.
exports.bookinstance\_list = function (req, res, next) {
  BookInstance.find()
    .populate("book")
    .exec(function (err, list\_bookinstances) {
      if (err) {
        return next(err);
      }
      // Successful, so render
      res.render("bookinstance\_list", {
        title: "Book Instance List",
        bookinstance\_list: list_bookinstances,
      });
    });
};
```

Чтобы вернуть все объекты `BookInstance,` метод использует функцию `find()` модели. Далее в цепочке вызывается метод `populate()` с аргументом - полем `book,` что приводит к замене идентификатора id, хранящегося для каждого экземпляра `BookInstance` полным документом `Book`.

При удаче, колбэк-функция, переданная запросу, заполняет шаблон **bookinstance\_list**(.pug), передав переменные `title` и `bookinstance_list`.

Представление
-------------

Создайте файл **/views/bookinstance\_list.pug** и скопируйте в него текст, приведённый ниже.

```js
extends layout

block content
  h1= title

  ul
    each val in bookinstance_list
      li
        a(href=val.url) #{val.book.title} : #{val.imprint} -
        if val.status=='Available'
          span.text-success #{val.status}
        else if val.status=='Maintenance'
          span.text-danger #{val.status}
        else
          span.text-warning #{val.status}
        if val.status!='Available'
          span  (Due: #{val.due_back} )

    else
      li There are no book copies in this library.
```

This view is much the same as all the others. It extends the layout, replacing the *content* block, displays the `title` passed in from the controller, and iterates through all the book copies in `bookinstance_list`. For each copy we display its status (colour coded) and if the book is not available, its expected return date. One new feature is introduced—we can use dot notation after a tag to assign a class. So `span.text-success` will be compiled to `<span class="text-success">` (and might also be written in Pug as `span(class="text-success")`.

What does it look like?
-----------------------

Run the application, open your browser to http://localhost:3000/, then select the *All book-instances* link. If everything is set up correctly, your site should look something like the following screenshot.

![BookInstance List Page - Express Local Library site](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page/locallibary_express_bookinstance_list.png)

Next steps
----------

* Return to Express Tutorial Part 5: Displaying library data.
* Proceed to the next subarticle of part 5: Date formatting using moment.


Основы шаблонов - Изучение веб-разработки

Основы шаблонов
===============

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

Выбор шаблонов Express
----------------------

В Express можно использовать много движков отображающих шаблонов ( template rendering engines). В этом руководстве для шаблонов будет использован Pug (ранее известный как Jade) . Это наиболее популярный в Node язык шаблонов, который о себе заявляет так: чистый, чувствительный к пробелам синтаксис для написания HTML, на который сильно повлиял Haml.

Разные языки шаблонов используют различные подходы для определения внешнего вида и разметки позиций для данных—некоторые используют HTML для определения внешнего вида, тогда как другие применяют различные форматы разметки, которые затем должны компилироваться в HTML. Pug - второго типа; он использует *представление* (*representation)* HTML, в котором первое слово в каждой строке обычно представляет элемент HTML, а отступы в следующих строках применяются, чтобы представить вложенные элементы. Результатом является определение страницы, которое транслируется непосредственно в HTML, и которое, вероятно, более краткое и легче читается.

**Примечание:** недостаток применения *Pug* - это чувствительность к отступам и пробелам (если добавить лишний пробел в "плохом" месте, можно получить невразумительный код ошибки). Однако, если ваши шаблоны уже действуют, их очень легко читать и поддерживать.

Конфигурация шаблона
--------------------

Когда создавался каркас (the skeleton website) веб-сайта *LocalLibrary,* он был настроен на использование Pug . Можно было заметить, что модуль pug включён в зависимости в файле **package.json**, и установлен (app.set(...)) как движок представлений в файле **app.js**. Эта установка показывает,, что движок представлений - pug, и что *Express* должен искать шаблоны в подкаталоге **/views**.

```js
// View engine setup.
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
```

Если посмотреть содержимое каталога **views**, можно увидеть файлы с расширением .pug, в которых шаблоны представлений по умолчанию. Это представление для домашней страницы (**index.pug**) и базовый шаблон (**layout.pug**), который следует заменить нашим содержимым.
```

/express-locallibrary-tutorial  //the project root
  /views
    error.pug
    index.pug
    layout.pug
```

Синтаксис шаблонов
------------------

Пример файла шаблона (ниже) демонстрирует многие наиболее полезные черты Pug.

Сначала отметим, что файл отражает структуру типового HTML-файла, причём первое слов в (почти) каждой строке является элементом HTML, а отступы используются, чтобы показать вложенные элементы. Так, например, элемент `body` находится внутри элемента `html`, а элементы `p` (параграфы) - внутри элемента `body,` и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.

```pug
doctype html
html(lang="en")
  head
    title= title
    script(type='text/javascript').
  body
    h1= title

    p This is a line with #[em some emphasis] and #[strong strong text] markup.
    p This line has un-escaped data: !{'<em> is emphasised</em>'} and escaped data: #{'<em> is not emphasised</em>'}.
      | This line follows on.
    p= 'Evaluated and <em>escaped expression</em>:' + title

    <!-- You can add HTML comments directly -->
    // You can add single line JavaScript comments and they are generated to HTML comments
    //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML

    p A line with a link
      a(href='/catalog/authors') Some link text
      |  and some extra text.

    #container.col
      if title
        p A variable named "title" exists.
      else
        p A variable named "title" does not exist.
      p.
 Pug is a terse and simple template language with a
 strong focus on performance and powerful features.

    h2 Generate a list

    ul
      each val in [1, 2, 3, 4, 5]
        li= val
```

Атрибуты элементов определены в скобках после соответствующих элементов. В скобках располагается список пар \*имя атрибута=значение,\*причём элементы списка разделяются запятой или пробелом. Например:

* `script(type='text/javascript')`, `link(rel='stylesheet', href='/stylesheets/style.css')`
* `meta(name='viewport' content='width=device-width initial-scale=1')`

Значения всех атрибутов *экранируются* (т.е. такие символы как "`>`" заменяются эквивалентными кодами HTML как "`>"`) , чтобы предотвратить JavaScript инъекции и межсайтовые атаки.

Если после тэга стоит знак = , следующий текст рассматривается как *выражение* JavaScript. Например, ниже в первой строке, содержимое тэга `h1` будет *переменной* `title` (которая определена в файле или передана в шаблон из Express). Во второй строке содержимое параграфа - это текстовая строка, соединённая с переменной `title` . В каждом из случаев поведение по умолчанию - экранировать строки.

```pug
h1= title
p= 'Evaluated and <em>escaped expression</em>:' + title
```

Если после тэга знак = отсутствует, тогда содержимое рассматривается как обычный текст. Внутри текста можно вставить экранированные или неэкранированные данные, применяя синтаксис `#{}` и `!{}`, как показано ниже. В простой текст можно также вставлять "сырой" HTML.

```pug
p This is a line with #[em some emphasis] and #[strong strong text] markup.
p This line has an un-escaped string: !{'<em> is emphasised</em>'}, an escaped string: #{'<em> is not emphasised</em>'}, and escaped variables: #{title}.
```

**Примечание:** Почти всегда желательно экранировать данные, полученные от пользователей (при помощи синтаксиса **`#{}`** ). Данные, которым можно верить (т.е. подсчитанное количество записей, могут быть выведены без экранирования значений.

Можно использовать символ конвейера ('**|**') в начале строки, чтобы отметить простой текст ("plain text"). Например, дополнительный текст, приведённый ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.

```pug
a(href='http://someurl/') Link text
| Plain text
```

Pug позволяет выполнять условные операции `if`, `else` , `else if` и `unless`— пример приведён ниже:

```pug
if title
  p Переменная с именем "title" существует
else
  p Переменной с именем "title" не существует
```

Можно также выполнять циклы (итерации), применяя синтаксис `each-in` или `while` . Фрагмент кода (ниже) содержит цикл по элементам массива, чтобы показать список элементов (отметим применение 'li=' для оценки "val" как переменной). Значение итератора val может быть также передано в шаблон как переменная!

```pug
ul
  each val in [1, 2, 3, 4, 5]
    li= val
```

Синтаксис разрешает также комментарии (которые попадут в результат или нет, по вашему желанию), смеси для создания повторно используемых блоков кода, операторы выбора case, и много другого. Более подробная информация - в документации The Pug docs.

Расширение шаблонов
-------------------

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

Например, базовый шаблон **layout.pug,** созданный в каркасе проекта, имеет такой вид:

```pug
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content
```

Тэг `block` применён для отметки разделов контента, которые могут быть заменены в производных шаблона (если блок не переопределяется, будет использования его реализация в базовом классе).

Умолчание для **index.pug** (созданный для каркаса проекта) показывает, как можно заменить базовый шаблон. Тэг `extends` идентифицирует базовый шаблон, который следует использовать, а затем мы используем `block section_name,` чтобы отметить новый контент раздела, который мы заменяем.

```pug
extends layout

block content
  h1= title
  p Welcome to #{title}
```

Next steps
----------

* Return to Express Tutorial Part 5: Displaying library data.
* Proceed to the next subarticle of part 5: The LocalLibrary base template.


Страница списка книг - Изучение веб-разработки

Страница списка книг
====================

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

Контроллер
----------

Функция контроллера списка книг должна получить список всех объектов `Book` в базе данных, а затем передать их для отрисовки шаблона.

Откройте файл **/controllers/bookController.js**. Найдите экспортируемый метод контроллера `book_list()` и замените его следующим кодом.

```js
// Display list of all Books.
exports.book\_list = function (req, res, next) {
  Book.find({}, "title author")
    .populate("author")
    .exec(function (err, list\_books) {
      if (err) {
        return next(err);
      }
      //Successful, so render
      res.render("book\_list", { title: "Book List", book\_list: list_books });
    });
};
```

Метод использует функцию модели `find()` для возврата всех объектов `Book`, выбрав для возврата только заголовок и автора, поскольку нам не нужны другие поля (он также вернёт `_id` и виртуальные поля). Здесь мы также вызываем `populate()` on `Book`, указывая поле `author` —это заменит сохранённый идентификатор автора книги полными сведениями об авторе.

При успешном выполнении, колбэк передаст запрос на отрисовку шаблона **book\_list**(.pug), передаст `title` и `book_list` (список книг с автором) в качестве переменных.

Представление
-------------

Создайте файл **/views/book\_list.pug** и скопируйте в него текст ниже.

```js
extends layout

block content
  h1= title

  ul
  each book in book_list
    li
      a(href=book.url) #{book.title}
      |  (#{book.author.name})

  else
    li There are no books.
```

View расширит базовый шаблон **layout.pug** и переопределит `block` с именем '**content**'. Он отображает `title` который мы передали из контроллера (с помощью метода `render()` ), а затем перебирает переменную `book_list` используя синтаксис `each`-`in`-`else` . Для каждой книги создаётся элемент списка, отображающий название книги в виде ссылки на страницу сведений о книге, за которой следует имя автора. Если в `book_list` нет книг, то выполняется `else`, и отображается текст "нет книг".'

>
> \*\*Примечание:\*\*Мы используем `book.url` для предоставления ссылки на подробную запись для каждой книги (мы реализовали этот маршрут, но не страницу). Это виртуальное свойство модели `Book` , которая использует поле `_id` для создания уникального URL.
>
>
>

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

На что это похоже?
------------------

Запустите приложение (смотрите тестирование маршрутов для соответствующей команды) и откройте ваш браузер по адресу: http://localhost:3000/. Затем выберите ссылку: *All books*. Если все сделано корректно, то вы должны увидеть нечто подобное скриншоту ниже.

![Book List Page - Express Local Library site](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page/locallibary_express_book_list.png)

Next steps
----------

* Return to Express Tutorial Part 5: Displaying library data.
* Proceed to the next subarticle of part 5: BookInstance list page.


Базовый шаблон LocalLibrary - Изучение веб-разработки

Базовый шаблон LocalLibrary
===========================

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

Откройте файл **/views/layout.pug** и замените его содержимое следующим.

```pug
doctype html
html(lang='en')
  head
    title= title
    meta(charset='utf-8')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css')
    script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js')
    script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    div(class='container-fluid')
      div(class='row')
        div(class='col-sm-2')
          block sidebar
            ul(class='sidebar-nav')
              li
                a(href='/catalog') Home
              li
                a(href='/catalog/books') All books
              li
                a(href='/catalog/authors') All authors
              li
                a(href='/catalog/genres') All genres
              li
                a(href='/catalog/bookinstances') All book-instances
              li
                hr
              li
                a(href='/catalog/author/create') Create new author
              li
                a(href='/catalog/genre/create') Create new genre
              li
                a(href='/catalog/book/create') Create new book
              li
                a(href='/catalog/bookinstance/create') Create new book instance (copy)

        div(class='col-sm-10')
          block content
```

Шаблон использует (и включает) JavaScript и CSS из Bootstrap , что позволяет улучшить макет и представление HTML-страницы. Применение Bootstrap или другого клиентского фреймворка - быстрый способ создать привлекательную, хорошо масштабируемую страницу. Кроме того, это позволяет получить представление страницы, не вдаваясь в детали - мы можем уделить все внимание коду на стороне сервера!

Макет представляется достаточно очевидным, если вы уже прочли статью Основы шаблонов (Template primer) выше. Обратите внимание на использование `block content` в качестве места для размещения контента отдельных страниц.

Базовый шаблон также ссылается на локальный файл стилей (**style.css**), что обеспечивает дополнительное управление внешним видом. Откройте **/public/stylesheets/style.css** и замените его содержимое таким текстом:

```css
.sidebar-nav {
  margin-top: 20px;
  padding: 0;
  list-style: none;
}
```

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

Следующие шаги
--------------

* Вернуться к Учебник Express часть 5: Отображение данных библиотеки.
* Перейти к следующему подразделу Домашняя страница.


Страница с подробностями жанров - Изучение веб-разработки

Страница с подробностями жанров
===============================

Страница "подробности" (*detail)* для жанров должна показывать информацию для отдельного жанра по его автоматически генерируем идентификатору `_id`. Должно быть показано название жанра и список книг этого жанра, со ссылками на страницу с детальной информацией для каждой книги.

Controller
----------

Откройте файл **/controllers/genreController.js** и импортируйте модули *async* и *Book* в первых строках файла.

```js
var Book = require("../models/book");
var async = require("async");
```

Найдите экспортируемый метод контроллера `genre_detail()` и замените его следующим кодом:

```js
// Display detail page for a specific Genre.
exports.genre\_detail = function (req, res, next) {
  async.parallel(
    {
      genre: function (callback) {
        Genre.findById(req.params.id).exec(callback);
      },

      genre\_books: function (callback) {
        Book.find({ genre: req.params.id }).exec(callback);
      },
    },
    function (err, results) {
      if (err) {
        return next(err);
      }
      if (results.genre == null) {
        // No results.
        var err = new Error("Genre not found");
        err.status = 404;
        return next(err);
      }
      // Successful, so render
      res.render("genre\_detail", {
        title: "Genre Detail",
        genre: results.genre,
        genre\_books: results.genre_books,
      });
    },
  );
};
```

Метод использует `async.parallel()` для параллельного запроса названия жанра и связанных с ним книг, причём колбэк-функция возвращает страницу, когда (если) оба запроса завершились успешно.

The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (**/genre/:id**). The ID is accessed within the controller via the request parameters: `req.params.id`. It is used in `Genre.findById()` to get the current genre. It is also used to get all `Book` objects that have the genre ID in their `genre` field: `Book.find({ 'genre': req.params.id })`.

**Примечание:** If the genre does not exist in the database (i.e. it may have been deleted) then `findById()` will return successfully with no results. In this case we want to display a "not found" page, so we create an `Error` object and pass it to the `next` middleware function in the chain.

```js
if (results.genre == null) {
  // No results.
  var err = new Error("Genre not found");
  err.status = 404;
  return next(err);
}
```

The message will then propagate through to our error handling code (this was set up when we generated the app skeleton - for more information see Handling Errors).

The rendered view is **genre\_detail** and it is passed variables for the `title`, `genre` and the list of books in this genre (`genre_books`).

View
----

Create **/views/genre\_detail.pug** and fill it with the text below:

```js
extends layout

block content

  h1 Genre: #{genre.name}

  div(style='margin-left:20px;margin-top:20px')

    h4 Books

    dl
      each book in genre_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

      else
        p This genre has no books
```

The view is very similar to all our other templates. The main difference is that we don't use the `title` passed in for the first heading (though it is used in the underlying **layout.pug** template to set the page title).

What does it look like?
-----------------------

Run the application and open your browser to http://localhost:3000/. Select the *All genres* link, then select one of the genres (e.g. "Fantasy"). If everything is set up correctly, your page should look something like the following screenshot.

![Genre Detail Page - Express Local Library site](/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page/locallibary_express_genre_detail.png)

**Примечание:** You might get an error similar to this:

```bash
Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "\_id" for model "Genre"
```

This is a mongoose error coming from the **req.params.id**. To solve this problem, first you need to require mongoose on the **genreController.js** page like this:

```js
var mongoose = require("mongoose");
```

Then use **mongoose.Types.ObjectId()** to convert the id to a that can be used. For example:

```js
exports.genre\_detail = function(req, res, next) {
    var id = mongoose.Types.ObjectId(req.params.id);
    ...
```

Next steps
----------

* Return to Express Tutorial Part 5: Displaying library data.
* Proceed to the next subarticle of part 5: Book detail page (en-US).