创建 BookInstance 表单

本小节演示如何定义一个页面/表单来创建BookInstance对象。这与我们用来创建Book对象的表单非常相似。

导入验证和清理方法

打开/controllers/bookinstanceController.js,并在文件顶部添加以下几行

js
const { body, validationResult } = require("express-validator");

控制器 - 获取路由

在文件顶部,引入Book模块(需要引入,因为每个BookInstance都与特定的Book相关联)。

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

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

js
// Display BookInstance create form on GET.
exports.bookinstance_create_get = asyncHandler(async (req, res, next) => {
  const allBooks = await Book.find({}, "title").sort({ title: 1 }).exec();

  res.render("bookinstance_form", {
    title: "Create BookInstance",
    book_list: allBooks,
  });
});

控制器获取所有书籍的排序列表(allBooks),并通过book_list将其传递给视图bookinstance_form.pug(以及一个title)。请注意,当我们第一次显示此表单时,还没有选择任何书籍,因此我们不会将selected_book变量传递给render()。因此,selected_book在模板中将具有undefined的值。

控制器 - 发布路由

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

js
// Handle BookInstance create on POST.
exports.bookinstance_create_post = [
  // Validate and sanitize fields.
  body("book", "Book must be specified").trim().isLength({ min: 1 }).escape(),
  body("imprint", "Imprint must be specified")
    .trim()
    .isLength({ min: 1 })
    .escape(),
  body("status").escape(),
  body("due_back", "Invalid date")
    .optional({ values: "falsy" })
    .isISO8601()
    .toDate(),

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

    // Create a BookInstance object with escaped and trimmed data.
    const 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.
      const allBooks = await Book.find({}, "title").sort({ title: 1 }).exec();

      res.render("bookinstance_form", {
        title: "Create BookInstance",
        book_list: allBooks,
        selected_book: bookInstance.book._id,
        errors: errors.array(),
        bookinstance: bookInstance,
      });
      return;
    } else {
      // Data from form is valid
      await bookInstance.save();
      res.redirect(bookInstance.url);
    }
  }),
];

此代码的结构和行为与创建其他对象相同。首先,我们验证并清理数据。如果数据无效,则重新显示表单以及用户最初输入的数据和错误消息列表。如果数据有效,则保存新的BookInstance记录,并将用户重定向到详细信息页面。

视图

创建/views/bookinstance_form.pug并将下面的文本复制到其中。

pug
extends layout

block content
  h1=title

  form(method='POST')
    div.form-group
      label(for='book') Book:
      select#book.form-control(name='book' required)
        option(value='') --Please select a book--
        for book in book_list
          if selected_book==book._id.toString()
            option(value=book._id, selected) #{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 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_yyyy_mm_dd))

    div.form-group
      label(for='status') Status:
      select#status.form-control(name='status' required)
        option(value='') --Please select a status--
        each val in ['Maintenance', 'Available', 'Loaned', 'Reserved']
          if undefined===bookinstance || bookinstance.status!=val
            option(value=val)= val
          else
            option(value=val selected)= val

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

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

注意:以上模板硬编码了状态值(维护中、可用等),并且不会“记住”用户输入的值。如果您愿意,可以考虑重新实现列表,从控制器中传入选项数据,并在重新显示表单时设置选定的值。

视图的结构和行为与book_form.pug模板几乎相同,因此我们不会详细介绍。需要注意的一点是,如果我们正在为现有实例填充日期输入,则我们设置“到期归还”日期为bookinstance.due_back_yyyy_mm_dd的那一行。

pug
input#due_back.form-control(type='date', name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back_yyyy_mm_dd))

日期值必须设置为YYYY-MM-DD格式,因为这是<input>元素(type="date"所期望的,但是日期不是以这种格式存储的,因此我们必须在设置控件中的值之前进行转换。due_back_yyyy_mm_dd()方法在下一节中添加到BookInstance模型中。

模型 - 虚拟 due_back_yyyy_mm_dd() 方法

打开定义BookInstanceSchema模型的文件(models/bookinstance.js)。添加下面显示的due_back_yyyy_mm_dd()虚拟函数(在due_back_formatted()虚拟函数之后)

js
BookInstanceSchema.virtual("due_back_yyyy_mm_dd").get(function () {
  return DateTime.fromJSDate(this.due_back).toISODate(); // format 'YYYY-MM-DD'
});

它是什么样子的?

运行应用程序并在浏览器中打开https://127.0.0.1:3000/。然后选择创建新的图书实例(复制)链接。如果一切设置正确,您的网站应该如下面的屏幕截图所示。提交有效的BookInstance后,它应该会被保存,并且您将被带到详细信息页面。

Create BookInstance of the Local library application screenshot from localhost:3000. The page is divided into two columns. The narrow left column has a vertical navigation bar with 10 links separated into two sections by a light-colored horizontal line. The top section link to already created data. The bottom links go to create new data forms. The wide right column has the create book instance form with a 'Create BookInstance' heading and four input fields labeled 'Book', 'Imprint', 'Date when book available' and 'Status'. The form is filled. There is a 'Submit' button at the bottom of the form.

后续步骤