更新书籍表格

本节文章展示如何定义一个更新`Book`对象的页面。更新书籍时的表单处理与创建书籍时的处理方式非常相似,区别在于您需要在`GET`路由中使用数据库中的值填充表单。

控制器 - 获取路由

打开** /controllers/bookController.js **。找到导出的`book_update_get()`控制器方法,并将其替换为以下代码。

js
// Display book update form on GET.
exports.book_update_get = asyncHandler(async (req, res, next) => {
  // Get book, authors and genres for form.
  const [book, allAuthors, allGenres] = await Promise.all([
    Book.findById(req.params.id).populate("author").exec(),
    Author.find().sort({ family_name: 1 }).exec(),
    Genre.find().sort({ name: 1 }).exec(),
  ]);

  if (book === null) {
    // No results.
    const err = new Error("Book not found");
    err.status = 404;
    return next(err);
  }

  // Mark our selected genres as checked.
  allGenres.forEach((genre) => {
    if (book.genre.includes(genre._id)) genre.checked = "true";
  });

  res.render("book_form", {
    title: "Update Book",
    authors: allAuthors,
    genres: allGenres,
    book: book,
  });
});

该控制器从 URL 参数 ( `req.params.id` ) 获取要更新的`Book`的 id。它`await` `Promise.all()` 返回的 promise 以获取指定的`Book`记录(填充其流派和作者字段)以及所有`Author`和`Genre`记录。

当操作完成时,该函数检查是否找到了任何书籍,如果没有找到任何书籍,则将错误“书籍未找到”发送到错误处理中间件。

**注意:**对于搜索,找不到任何书籍结果**不是错误** - 但对于此应用程序而言,这是错误,因为我们知道必须存在匹配的书籍记录!上面的代码在回调中比较了 ( `book===null` ) ,但它也可以同样地将方法 orFail() 连接到查询中。

然后,我们将当前选择的流派标记为选中,并渲染**book_form.pug**视图,传递`title`、book、所有`authors`和所有`genres`的变量。

控制器 - 发布路由

找到导出的`book_update_post()`控制器方法,并将其替换为以下代码。

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

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

  // Process request after validation and sanitization.
  asyncHandler(async (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.
    const 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
      const [allAuthors, allGenres] = await Promise.all([
        Author.find().sort({ family_name: 1 }).exec(),
        Genre.find().sort({ name: 1 }).exec(),
      ]);

      // Mark our selected genres as checked.
      for (const genre of allGenres) {
        if (book.genre.indexOf(genre._id) > -1) {
          genre.checked = "true";
        }
      }
      res.render("book_form", {
        title: "Update Book",
        authors: allAuthors,
        genres: allGenres,
        book: book,
        errors: errors.array(),
      });
      return;
    } else {
      // Data from form is valid. Update the record.
      const updatedBook = await Book.findByIdAndUpdate(req.params.id, book, {});
      // Redirect to book detail page.
      res.redirect(updatedBook.url);
    }
  }),
];

这与创建`Book`时使用的 post 路由非常相似。首先,我们验证和清理来自表单的书籍数据,并使用它创建一个新的`Book`对象(将其`_id`值设置为要更新的对象的 id)。如果验证数据时出现错误,我们将重新渲染表单,并额外显示用户输入的数据、错误以及流派和作者列表。如果没有错误,我们将调用`Book.findByIdAndUpdate()`来更新`Book`文档,然后重定向到其详细信息页面。

视图

无需更改表单的视图(** /views/book_form.pug **),因为同一个模板适用于创建和更新书籍。

添加一个更新按钮

打开**book_detail.pug**视图,并确保页面底部有用于删除和更新书籍的链接,如下所示。

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

您现在应该能够从“书籍详细信息”页面更新书籍。

它看起来像什么?

运行应用程序,在浏览器中打开`https://127.0.0.1:3000/`,选择“所有书籍”链接,然后选择特定书籍。最后,选择“更新书籍”链接。

该表单应与“创建书籍”页面看起来完全一样,只是标题为“更新书籍”,并且预先填充了记录值。

The update book section of the Local library application. The left column has a vertical navigation bar. The right column has a form to update the book with an heading that reads 'Update book'. There are five input fields labelled Title, Author, Summary, ISBN, Genre. Genre is a checkbox option field. There is a button labelled 'Submit' at the end.

**注意:**更新其他对象的页面可以用相同的方式实现。我们把它留作一个挑战。

下一步