From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../displaying_data/author_list_page/index.html | 85 ++++++++++++ .../displaying_data/book_list_page/index.html | 68 ++++++++++ .../bookinstance_list_page/index.html | 69 ++++++++++ .../date_formatting_using_moment/index.html | 60 +++++++++ .../flow_control_using_async/index.html | 138 +++++++++++++++++++ .../displaying_data/genre_detail_page/index.html | 121 +++++++++++++++++ .../displaying_data/home_page/index.html | 134 ++++++++++++++++++ .../express_nodejs/displaying_data/index.html | 83 ++++++++++++ .../locallibrary_base_template/index.html | 69 ++++++++++ .../displaying_data/template_primer/index.html | 149 +++++++++++++++++++++ 10 files changed, 976 insertions(+) create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html (limited to 'files/ru/learn/server-side/express_nodejs/displaying_data') diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..2e1edbc625 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,85 @@ +--- +title: Список авторов. Тест - список жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +

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

+ +

Контроллер

+ +

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

+ +

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

+ +
// 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() callback-функция имеет первый параметр- объект ошибок  (или null) и второй параметр - список всех авторов, если ошибок не было. При ошибках вызывается следующая функция промежуточного слоя  с полученным значением объекта ошибок, а если ошибок не было, отображается шаблон author_list(.pug), передавая странице title и список авторов (author_list).

+ +

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

+ +

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

+ +
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

+ +
+

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

+ +

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

+
+ +

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

+ +

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

+ +

Genre List - Express Local Library site

+ +

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

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

Далее

+ +

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

+ +

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

diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..b5a4400d90 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,68 @@ +--- +title: Страница списка книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +

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

+ +

Контроллер

+ +

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

+ +

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

+ +
// 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 и скопируйте в него текст ниже.

+ +
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

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..512e78d040 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,69 @@ +--- +title: Список экземпляров книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +

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

+ +

Контроллер

+ +

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

+ +

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

+ +
// 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.

+ +

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

+ +

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

+ +

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

+ +
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

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..58f297ce95 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,60 @@ +--- +title: Форматирование даты при помощи moment +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_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

+ +

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

+ +
npm install moment
+ +

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

+ +
    +
  1. Откройте файл ./models/bookinstance.js.
  2. +
  3. В начало файла введите строку для импортирования moment. +
    var moment = require('moment');
    +
  4. +
+ +

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

+ +
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.

+ +
      if val.status!='Available'
+        //span  (Due: #{val.due_back} )
+        span  (Due: #{val.due_back_formatted} )       
+ +

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

+ +

 

+ +

Далее

+ + + +

 

diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..32100db740 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,138 @@ +--- +title: Асинхронное управление потоками при помощи async +slug: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +tags: + - Node + - Часть 5 +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +

Код контроллера для некоторых страниц библиотеки будет зависеть от результатов многих асинхронных запросов, которые должны выполняться в определенном порядке или параллельно. Для того, чтобы управлять потоком выполнения, и выводить страницы, когда получена вся необходимая информация, будет использован async - известный модуль node.

+ +
+

Note:  В JavaScript существует много других способов управления аснхронным поведением и потоком выполнения, включая такой относительно  новый элемент языка JacaScript как Promises (обещания, промисы).

+
+ +

Модуль Async имеет массу полезных методов (см. документациюt the documentation). Вот некоторые наиболее важные функции:

+ + + +

Почему это необходимо?

+ +

Большинство методов, которые используются в  Express - асинхронные - вы определяете выполняемую операцию, передавая  callback-функцию. Метод завершается немедленно, а  callback-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в Express, callback-функция передает значение ошибки error  как первый параметр (или null при успехе) и результат функции (если есть) как второй параметр.

+ +

Если контроллер должен выполнить только одну асинхронную операцию, чтобы получить информацию для представления страницы, то реализация проста - мы просто представляем шаблон в колбэке. Фрагмент кода (ниже) демонстрирует это для функции, которая подсчитывает количество элементов модкли SomeModel (применяя метод Mongoose count() ):

+ +
exports.some_model_count = function(req, res, next) {
+
+  SomeModel.count({ a_model_field: 'match_value' }, function (err, count) {
+    // ... сделать что-то, если ошибка
+
+    // При успехе представить результат, передав count в render-функцию (здесь - как переменную 'data').
+    res.render('the_template', { data: count } );
+  });
+}
+
+ +

Однако что, если требуется сделать множественные асинхронные запросы, и результат нельзя представить, пока не завершились все операции? Наивная реализация могла бы использовать "венок" запросов, запуская последующие запросы в колбэках предыдущих, и представляя ответ в последнем колбэке. Проблема такого подхода состоит в том, что запросы должны вапольняться последовательно, хотя, вероятно, было бы более эффективно выполнять их параллельно. Это также может привести к усложненному вложенному коду, что обычно называют адом обратных вызовов ( callback hell ).

+ +

Намного лучше было бы выполнять все запросы параллельно, и иметь единственную callback-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль Async делает легким и простым!

+ +

Параллельные асинхронные операции

+ +

Метод async.parallel() используется для параллельного выполнения нескольких асинхронных операций.

+ +

Первый аргумент в async.parallel() - это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает callback-функцию callback(err, result) , которую она должна вызвать при завершении, с ошибкой err (может быть null) и, возможно, со значением результата results.

+ +

Возможный второй аргумент для  async.parallel() - это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передается массив асинхронных функций, итоговая callback-функция будет вызвана с массивом результатов). Если любая из параллельных функций сообщила об ошибке, сразу вызывается итоговая callback-функция, которая возвращает ошибку.

+ +

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

+ +
async.parallel({
+  one: function(callback) { ... },
+  two: function(callback) { ... },
+  ...
+  something_else: function(callback) { ... }
+  },
+  // optional callback
+  function(err, results) {
+    // 'results' равны: {one: 1, two: 2, ..., something_else: some_value}
+  }
+);
+ +

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

+ +

Последовательные асинхронные операции

+ +

Для выполнения нескольких асинхронных операций последовательно используется метод async.series() , при этом последующие функции не зависят от результатов предыдущих функций. Метод определяется и ведет себя так же, как и async.parallel().

+ +
async.series({
+  one: function(callback) { ... },
+  two: function(callback) { ... },
+  ...
+  something_else: function(callback) { ... }
+  },
+  // optional callback after the last asynchronous function completes.
+  function(err, results) {
+    // 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} 
+  }
+);
+ +
+

Заметка: Спецификация языка ECMAScript (JavaScript) устанавливает, что порядок  в перечислении объектов не определен, поэтому возможно, что функции не будут вызываться в том порядке, в котором вы их задали на всех платформах. Если порядок вызова действительно важен, вместо объекта следует передавать массив, как показано ниже.

+
+ +
async.series([
+  function(callback) {
+    // do some stuff ...
+    callback(null, 'one');
+  },
+  function(callback) {
+    // do some more stuff ... 
+    callback(null, 'two');
+  }
+ ],
+  // optional callback
+  function(err, results) {
+  // results is now equal to ['one', 'two'] 
+  }
+);
+ +

Последовательные зависимые асинхронные операции

+ +

Выполнение нескольких асинхронных операций последовательно, когда каждая операция зависит от результатов предыдущих операций, осуществляется методом async.waterfall().

+ +

Функции-callback, которая вызываются асинхронными функциями , содержит null как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а callback-функция является последним аргументом. Когда  операции завершаются, вызывается финальная callback-функция, аргументы которой - объект err и результат последней операции. Как это работает, станет более ясным после рассмотрения примера - фрагмента кода, приведенного ниже ( пример взят из документации async):

+ +
async.waterfall([
+  function(callback) {//первая функция в цепочке
+    callback(null, 'one', 'two');//результаты 'one' и 'two'
+  },
+  function(arg1, arg2, callback) {//вторая функция в цепочке
+    // arg1 равен 'one' , arg2 равен  'two' 
+    callback(null, 'three'); //результат 'three'
+  },
+  function(arg1, callback) {
+    // arg1 равен 'three'
+    callback(null, 'done'); //результат 'done'
+  }
+], function (err, result) {
+  // result равен 'done'
+}
+);
+ +

Установка async

+ +

Установим модуль async при помощи менеджера пакетов NPM, чтобы использовать его в своем коде. Это делается обычным способом - откроем окно команд в корне проекта LocalLibrary и введем команду:

+ +
npm install async
+ +

Дальнейшие шаги

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..be5bd57962 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,121 @@ +--- +title: Страница с подробностями жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +

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

+ +

Controller

+ +

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

+ +
var Book = require('../models/book');
+var async = require('async');
+
+ +

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

+ +
// 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() для параллельного запроса названия жанра и связанных с ним книг, причем callback-функция возвращает страницу, когда (если) оба запроса завершились успешно.

+ +

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 }).

+ +
+

Note: 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. 

+ +
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:

+ +
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

+ +
+

You might get an error similar to this:

+ +
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:

+ +
 var mongoose = require('mongoose');
+
+ +

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

+ +
exports.genre_detail = function(req, res, next) {
+    var id = mongoose.Types.ObjectId(req.params.id);
+    ...
+
+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..248187f1a5 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,134 @@ +--- +title: Home page +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +

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

+ +

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

+ +

Маршрут

+ +

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

+ +
// GET catalog home page.
+router.get('/', book_controller.index);  //This actually maps to /catalog/ because we import the route with a /catalog prefix
+ +

Параметр callback-функции определен в /controllers/bookController.js:

+ +
exports.index = function(req, res, next) {
+    res.send('NOT IMPLEMENTED: Site Home Page');
+}
+ +

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

+ +

Контроллер

+ +

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

+ +
+

Заметка: Количество экземпляров в каждой модели вычисляется при помощи метода countDocuments() . Он вызывается для модели с возможным набором условий, необходимых для проверки соответствия первому аргументу и callback-функции второго аргумента (обсуждалось ранее в "Использование базы данных с Mongoose" Using a Database (with Mongoose)), причем можно вернуть также запрос Query, а затем выполнить его позже при помощи callback. Эта  callback-функция будет выполняться, когда БД вернет количество записей.  Значение ошибки (or null) будет первым параметром, а количество записей (или null, если была ошибка) -  вторым параметром.

+ +
SomeModel.countDocuments({ a_model_field: 'match_value' }, function (err, count) {
+ // ... do something if there is an err
+ // ... do something with the count if there was no error
+ });
+
+ +

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

+ +
var Book = require('../models/book')
+
+exports.index = function(req, res, next) {
+ res.send('NOT IMPLEMENTED: Site Home Page');
+}
+ +

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

+ +
var Book = require('../models/book');
+var Author = require('../models/author');
+var Genre = require('../models/genre');
+var BookInstance = require('../models/bookinstance');
+
+var async = require('async');
+
+exports.index = function(req, res) {
+
+    async.parallel({
+        book_count: function(callback) {
+            Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection
+// countDocuments не работает, работает только просто count
+        },
+        book_instance_count: function(callback) {
+            BookInstance.countDocuments({}, callback);
+        },
+        book_instance_available_count: function(callback) {
+            BookInstance.countDocuments({status:'Available'}, callback);
+        },
+        author_count: function(callback) {
+            Author.countDocuments({}, callback);
+        },
+        genre_count: function(callback) {
+            Genre.countDocuments({}, callback);
+        }
+    }, function(err, results) {
+        res.render('index', { title: 'Local Library Home', error: err, data: results });
+    });
+};
+ +

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

+ +

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

+ +
+

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

+
+ +

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

+ +

Откройте файл  /views/index.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
+
+  if error
+    p Error getting dynamic content.
+  else
+    p The library has the following record counts:
+
+    ul
+      li #[strong Books:] !{data.book_count}
+      li #[strong Copies:] !{data.book_instance_count}
+      li #[strong Copies available:] !{data.book_instance_available_count}
+      li #[strong Authors:] !{data.author_count}
+      li #[strong Genres:] !{data.genre_count}
+ +

Представление несложное. Мы расширили базовый шаблон  layout.pug, переопределив блок (block) с именем 'content'. Первый заголовок h1 будет экранированным текстом - значением переменной title ,variable that  которая передается в функцию render() —заметьте, что применение 'h1='  говорит, что следующий текст рассматривается как выражение JavaScript. Затем расположен параграф, знакомящий с  LocalLibrary.

+ +

Под заголовком Dynamic content  мы проверяем, определена ли переданная из функции render() переменная error. Если да, отмечаем ошибку. Если нет, выводим ( как список) количества копий каждой модели, которые хранятся в переменной data.

+ +
+

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

+
+ +

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

+ +

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

+ +

Home page - Express Local Library site

+ +
+

Заметка:  Элементы бокового меню использовать еще нельзя, так как адреса, представления и шаблоны для этих страниц еще не определены. Если вы попытаетесь их использовать, будет выведено сообщение об ошибке, например,  вида "NOT IMPLEMENTED: Book list" (НЕ РЕАЛИЗОВАНО: список книг), в зависимости от выбранного элемента меню.  Эти строковые литералы (которые будут замещены действительными данными) были заданы в различных файлах контроллеров в каталоге "controllers".

+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..bb2e804d2e --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,83 @@ +--- +title: 'Учебник Express часть 5: Отображение данных библиотеки' +slug: Learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}
+ +

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

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

Обзор

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +
    +
  1. Aсинхронное управление потоками с помощью async
  2. +
  3. Пример шаблона
  4. +
  5. Базовые шаблоны LocalLibrary
  6. +
  7. Домашняя страница
  8. +
  9. Страница списка книг
  10. +
  11. Страница списка экземпляров книг
  12. +
  13. Форматирование даты с момента использования
  14. +
  15. Страница списка авторов и страница списка жанров
  16. +
  17. Страница сведений о жанре
  18. +
  19. Страница сведений о книге
  20. +
  21. Страница информации об авторе
  22. +
  23. Страница сведений об экземпляре книги и вызове
  24. +
+ +

Итог

+ +

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

+ +

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

+ +

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

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}

+ +

In this module

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..13c61ea6cb --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,69 @@ +--- +title: Базовый шаблон LocalLibrary +slug: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +

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

+ +

Откройте файл /views/layout.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 и замените его содержимое таким текстом:

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

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

+ +

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

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..3f537db354 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: Основы шаблонов +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +

Шаблон - это текстовый файл, определяющий структуру, или внешний вид выходного файла, с предусмотренными позициями, в которые будут помещаться данные при отображении по шаблону (в 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.

+ +
// 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, и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.

+ +
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
+ +

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

+ + + +

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

+ +

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

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

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

+ +
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"). Например, дополнительный текст, приведенный ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.

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

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

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

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

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

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

+ +

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

+ +

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

+ +

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

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

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

+ +

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

+ +
extends layout
+
+block content
+  h1= title
+  p Welcome to #{title}
+ +

Next steps

+ + -- cgit v1.2.3-54-g00ecf