From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../forms/create_bookinstance_form/index.html | 151 ++++++++++++ .../forms/create_genre_form/index.html | 199 +++++++++++++++ .../forms/delete_author_form/index.html | 165 +++++++++++++ .../server-side/express_nodejs/forms/index.html | 269 +++++++++++++++++++++ .../forms/update_book_form/index.html | 189 +++++++++++++++ 5 files changed, 973 insertions(+) create mode 100644 files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html (limited to 'files/ru/learn/server-side/express_nodejs/forms') diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html new file mode 100644 index 0000000000..13f409ea3c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html @@ -0,0 +1,151 @@ +--- +title: "Форма для создания\_BookInstance" +slug: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +--- +

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

+ +

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

+ +

Откройте /controllers/bookinstanceController.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).

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

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

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

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

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

+ +
+

Note: 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, он должен быть сохранен, и вы попадете на страницу сведений.

+ +

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html new file mode 100644 index 0000000000..c9407372c4 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html @@ -0,0 +1,199 @@ +--- +title: Create genre form +slug: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +--- +

В этом подразделе рассказано, как сделать страницу для создания жанра (Genre). Хорошая точка для старта, у жанра всего одно поле - name. Как и для любой другой страницы, здесь потребуется внести изменения в соответствующие маршрут, контроллер и шаблон (route, controller, view).

+ +

Import validation and sanitisation methods

+ +

To use the express-validator in our controllers we have to require the functions we want to use from the 'express-validator/check' and 'express-validator/filter' modules.

+ +

Open /controllers/genreController.js, and add the following line at the top of the file:

+ +
const validator = require('express-validator');
+
+ +

Controller—get route

+ +

Find the exported genre_create_get() controller method and replace it with the following code. This simply renders the genre_form.pug view, passing a title variable.

+ +
// Display Genre create form on GET.
+exports.genre_create_get = function(req, res, next) {
+  res.render('genre_form', { title: 'Create Genre' });
+};
+ +

Controller—post route

+ +

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

+ +
// Handle Genre create on POST.
+exports.genre_create_post =  [
+
+  // Validate that the name field is not empty.
+  validator.body('name', 'Genre name required').trim().isLength({ min: 1 }),
+
+  // Sanitize (escape) the name field.
+  validator.sanitizeBody('name').escape(),
+
+  // Process request after validation and sanitization.
+  (req, res, next) => {
+
+    // Extract the validation errors from a request.
+    const errors = validator.validationResult(req);
+
+    // Create a genre object with escaped and trimmed data.
+    var genre = new Genre(
+      { name: req.body.name }
+    );
+
+
+    if (!errors.isEmpty()) {
+      // There are errors. Render the form again with sanitized values/error messages.
+      res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+      return;
+    }
+    else {
+      // Data from form is valid.
+      // Check if Genre with same name already exists.
+      Genre.findOne({ 'name': req.body.name })
+        .exec( function(err, found_genre) {
+           if (err) { return next(err); }
+
+           if (found_genre) {
+             // Genre exists, redirect to its detail page.
+             res.redirect(found_genre.url);
+           }
+           else {
+
+             genre.save(function (err) {
+               if (err) { return next(err); }
+               // Genre saved. Redirect to genre detail page.
+               res.redirect(genre.url);
+             });
+
+           }
+
+         });
+    }
+  }
+];
+ +

The first thing to note is that instead of being a single middleware function (with arguments (req, res, next)) the controller specifies an array of middleware functions. The array is passed to the router function and each method is called in order.

+ +
+

Note: This approach is needed, because the sanitisers/validators are middleware functions.

+
+ +

The first method in the array defines a validator (validator.body()) from the validator  module to check that the name field is not empty (calling trim() to remove any trailing/leading whitespace before performing the validation). The  second method in the array (validator.sanitizeBody()) creates a sanitizer to escape() any dangerous  HTML characters in the name field.

+ +
// Validate that the name field is not empty.
+validator.body('name', 'Genre name required').isLength({ min: 1 }).trim(),
+
+// Sanitize (escape) the name field.
+validator.sanitizeBody('name').escape()
+ +

After specifying the validators and sanitizers we create a middleware function to extract any validation errors. We use isEmpty() to check whether there are any errors in the validation result. If there are then we render the form again, passing in our sanitised genre object and the array of error messages (errors.array()).

+ +
// Process request after validation and sanitization.
+(req, res, next) => {
+
+  // Extract the validation errors from a request.
+  const errors = validator.validationResult(req);
+
+  // Create a genre object with escaped and trimmed data.
+  var genre = new Genre(
+    { name: req.body.name }
+  );
+
+  if (!errors.isEmpty()) {
+    // There are errors. Render the form again with sanitized values/error messages.
+    res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+  return;
+  }
+  else {
+    // Data from form is valid.
+    ... <save the result/> ...
+  }
+};
+ +

If the genre name data is valid then we check if a Genre with the same name already exists (as we don't want to create duplicates). If it does, we redirect to the existing genre's detail page. If not, we save the new Genre and redirect to its detail page.

+ +
// Check if Genre with same name already exists.
+Genre.findOne({ 'name': req.body.name })
+  .exec( function(err, found_genre) {
+  if (err) { return next(err); }
+    if (found_genre) {
+      // Genre exists, redirect to its detail page.
+      res.redirect(found_genre.url);
+      }
+    else {
+      genre.save(function (err) {
+        if (err) { return next(err); }
+          // Genre saved. Redirect to genre detail page.
+          res.redirect(genre.url);
+        });
+    }
+});
+ +

This same pattern is used in all our post controllers: we run validators, then sanitisers,  then check for errors and either re-render the form with error information or save the data. 

+ +

View

+ +

The same view is rendered in both the GET and POST controllers/routes when we create a new Genre (and later on it is also used when we updateGenre). In the GET case the form is empty, and we just pass a title variable. In the POST case the user has previously entered invalid data—in the genre variable we pass back a sanitized version of the entered data and in the errors variable we pass back an array of error messages.

+ +
res.render('genre_form', { title: 'Create Genre'});
+res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+ +

Create /views/genre_form.pug and copy in the text below.

+ +
extends layout
+
+block content
+  h1 #{title}
+
+  form(method='POST' action='')
+    div.form-group
+      label(for='name') Genre:
+      input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name))
+    button.btn.btn-primary(type='submit') Submit
+
+  if errors
+   ul
+    for error in errors
+     li!= error.msg
+ +

Much of this template will be familiar from our previous tutorials. First, we extend the layout.pug base template and override the block named 'content'. We then have a heading with the title we passed in from the controller (via the render() method).

+ +

Next, we have the pug code for our HTML form that uses the POST method to send the data to the server, and because the action is an empty string, will send the data to the same URL as the page.

+ +

The form defines a single required field of type "text" called "name". The default value of the field depends on whether the genre variable is defined. If called from the GET route it will be empty as this is a new form. If called from a POST route it will contain the (invalid) value originally entered by the user.

+ +

The last part of the page is the error code. This simply prints a list of errors, if the error variable has been defined (in other words, this section will not appear when the template is rendered on the GET route).

+ +
+

Note: This is just one way to render the errors. You can also get the names of the affected fields from the error variable, and use these to control where the error messages are rendered, whether to apply custom CSS, etc.

+
+ +

What does it look like?

+ +

Run the application, open your browser to http://localhost:3000/, then select the Create new genre link. If everything is set up correctly, your site should look something like the following screenshot. After you enter a value, it should be saved and you'll be taken to the genre detail page.

+ +

Genre Create Page - Express Local Library site

+ +

The only error we validate against server-side is that the genre field must not be empty. The screenshot below shows what the error list would look like if you didn't supply a genre (highlighted in red).

+ +

+ +
+

Note: Our validation uses trim() to ensure that whitespace is not accepted as a genre name. We can also validate that the field is not empty on the client side by adding the value required='true' to the field definition in the form:

+ +
input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), required='true' )
+
+ +

Next steps

+ +
    +
  1. Return to Express Tutorial Part 6: Working with forms.
  2. +
  3. Proceed to the next sub article of part 6: Create Author form.
  4. +
diff --git a/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html new file mode 100644 index 0000000000..0e0fa6cdf3 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html @@ -0,0 +1,165 @@ +--- +title: Delete Author form +slug: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +--- +

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

+ +

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

+ +

Controller—get route

+ +

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

+ +
// Отображать форму для удаления автора 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 } );
+    });
+
+};
+ +

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

+ +
+

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

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

Controller—post route

+ +

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

+ +
// Обработчик удаления автора 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 и скопируйет текст ниже.

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

+ + + +

Добавление элемента управления delete

+ +

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

+ +
+

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

+
+ +

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

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

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

+ +

+ +

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

+ +

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

+ +

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

+ +

+ +

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

+ +

+ +
+

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

+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/index.html b/files/ru/learn/server-side/express_nodejs/forms/index.html new file mode 100644 index 0000000000..f877a6015c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/index.html @@ -0,0 +1,269 @@ +--- +title: 'Учебник Express часть 6: Работа с формами' +slug: Learn/Server-side/Express_Nodejs/forms +tags: + - Начинающим + - Сервер + - Формы +translation_of: Learn/Server-side/Express_Nodejs/forms +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}
+ +

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

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

Обзор

+ +

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

+ +

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

+ +

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

+ +
+

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

+
+ +

HTML Forms

+ +

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

+ +

Simple name field example in HTML form

+ +

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

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

+ + + +

Процесс обработки формы

+ +

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

+ +

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

+ +

+ +

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

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

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

+ +

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

+ +

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

+ +

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

+ + + +

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

+ +

Установка

+ +

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

+ +
npm install express-validator
+
+ +

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

+ +
+

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

+
+ +

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

+ +
const { body,validationResult } = require('express-validator/check');
+const { sanitizeBody } = require('express-validator/filter');
+
+ +

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

+ +

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

+ + + +

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

+ +

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

+ +

Дизайн формы

+ +

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

+ + + +

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

+ + + +
+

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

+
+ +

Маршруты

+ +

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

+ +

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

+ +
// 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 — Определение нашей страницы для создания объектов Genre.
  2. +
  3. Форма для cоздания Author — Определение страницы для создания объектов Author.
  4. +
  5. Форма для создания Book — Определение страницы/формы для создания объектов Book.
  6. +
  7. Форма для создания BookInstance — Определение страницы/формы для создания объектов BookInstance.
  8. +
  9. Форма для удаления Author — Определение страницы для удаления объектов Author.
  10. +
  11. Форма для обновления Book — Определение страницы для обновления объектов Book.
  12. +
+ +

Challenge yourself

+ +

Implement the delete pages for the Book, BookInstance, and Genre models, linking them from the associated detail pages in the same way as our Author delete page. The pages should follow the same design approach:

+ + + +

A few tips:

+ + + +

Implement the update pages for the BookInstance, Author, and Genre models, linking them from the associated detail pages in the same way as our Book update page.

+ +

A few tips:

+ + + +

Summary

+ +

Express, node, and third party packages on NPM provide everything you need to add forms to your website. In this article you've learned how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.

+ +

You should now understand how to add basic forms and form-handling code to your own node websites!

+ +

See also

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}

+ +

In this module

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html new file mode 100644 index 0000000000..16172605d1 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html @@ -0,0 +1,189 @@ +--- +title: Update Book form +slug: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +--- +

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

+ +

Controller—get route

+ +

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

+ +
// 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 (pаполнение полей жанра и автора) и список всех объектов Author и Genre. Когда все операции завершены, он помечает выбранные жанры как отмеченные, а затем отображает их в book_form.pug, передает переменные itle, book, всех authors, и всеgenres.

+ +

Controller—post route

+ +

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

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

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

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

+
+ +

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

+ +

Откройте book_detail.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' и предварительно заполнены значениями записей.

+ +

+ +
+

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

+
+ +

 

+ +

Next steps

+ + + +

 

-- cgit v1.2.3-54-g00ecf